fix: iot
This commit is contained in:
@@ -154,7 +154,7 @@ async function handleDelete(row: any) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDevice(row.id);
|
await deleteDevice(row.id);
|
||||||
message.success($t('common.delSuccess'));
|
message.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
@@ -175,7 +175,7 @@ async function handleDeleteBatch() {
|
|||||||
try {
|
try {
|
||||||
const ids = checkedRows.map((row: any) => row.id);
|
const ids = checkedRows.map((row: any) => row.id);
|
||||||
await deleteDeviceList(ids);
|
await deleteDeviceList(ids);
|
||||||
message.success($t('common.delSuccess'));
|
message.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function useIotHome() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 格式化数字 - 大数字显示为 K/M */
|
/** 格式化数字 - 大数字显示为 K/M */
|
||||||
export const formatNumber = (num: number): string => {
|
export function formatNumber(num: number): string {
|
||||||
if (num >= 1_000_000) {
|
if (num >= 1_000_000) {
|
||||||
return `${(num / 1_000_000).toFixed(1)}M`;
|
return `${(num / 1_000_000).toFixed(1)}M`;
|
||||||
}
|
}
|
||||||
@@ -110,4 +110,4 @@ export const formatNumber = (num: number): string => {
|
|||||||
return `${(num / 1000).toFixed(1)}K`;
|
return `${(num / 1000).toFixed(1)}K`;
|
||||||
}
|
}
|
||||||
return num.toString();
|
return num.toString();
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
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 { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
|
|
||||||
import { getSimpleProductList } from '#/api/iot/product/product';
|
import { getSimpleProductList } from '#/api/iot/product/product';
|
||||||
import { $t } from '#/locales';
|
|
||||||
import { getRangePickerDefaultProps } from '#/utils';
|
import { getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
/** 新增/修改固件的表单 */
|
/** 新增/修改固件的表单 */
|
||||||
@@ -157,51 +152,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Grid 配置项 */
|
|
||||||
export function useGridOptions(): VxeTableGridOptions<IoTOtaFirmwareApi.Firmware> {
|
|
||||||
return {
|
|
||||||
columns: useGridColumns(),
|
|
||||||
height: 'auto',
|
|
||||||
keepSource: true,
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async ({ page }, formValues) => {
|
|
||||||
return await getOtaFirmwarePage({
|
|
||||||
pageNo: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
...formValues,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
search: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除固件 */
|
|
||||||
export async function handleDeleteFirmware(
|
|
||||||
row: IoTOtaFirmwareApi.Firmware,
|
|
||||||
onSuccess: () => void,
|
|
||||||
) {
|
|
||||||
const hideLoading = message.loading({
|
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
|
||||||
duration: 0,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await deleteOtaFirmware(row.id as number);
|
|
||||||
message.success({
|
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
||||||
});
|
|
||||||
onSuccess();
|
|
||||||
} finally {
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
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 { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import Form from '../modules/OtaFirmwareForm.vue';
|
import Form from '../modules/OtaFirmwareForm.vue';
|
||||||
import {
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
handleDeleteFirmware,
|
|
||||||
useGridFormSchema,
|
|
||||||
useGridOptions,
|
|
||||||
} from './data';
|
|
||||||
|
|
||||||
defineOptions({ name: 'IoTOtaFirmware' });
|
defineOptions({ name: 'IoTOtaFirmware' });
|
||||||
|
|
||||||
@@ -35,21 +35,56 @@ function handleEdit(row: IoTOtaFirmwareApi.Firmware) {
|
|||||||
formModalApi.setData({ type: 'update', id: row.id }).open();
|
formModalApi.setData({ type: 'update', id: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除固件 */
|
|
||||||
async function handleDelete(row: IoTOtaFirmwareApi.Firmware) {
|
|
||||||
await handleDeleteFirmware(row, onRefresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 查看固件详情 */
|
/** 查看固件详情 */
|
||||||
function handleDetail(row: IoTOtaFirmwareApi.Firmware) {
|
function handleDetail(row: IoTOtaFirmwareApi.Firmware) {
|
||||||
formModalApi.setData({ type: 'view', id: row.id }).open();
|
formModalApi.setData({ type: 'view', id: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 删除固件 */
|
||||||
|
async function handleDelete(row: IoTOtaFirmwareApi.Firmware) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteOtaFirmware(row.id as number);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: useGridOptions(),
|
gridOptions: {
|
||||||
|
columns: useGridColumns(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getOtaFirmwarePage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<IoTOtaFirmwareApi.Firmware>,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ const firmwareStatisticsLoading = ref(false);
|
|||||||
const firmwareStatistics = ref<Record<string, number>>({});
|
const firmwareStatistics = ref<Record<string, number>>({});
|
||||||
|
|
||||||
/** 获取固件信息 */
|
/** 获取固件信息 */
|
||||||
const getFirmwareInfo = async () => {
|
async function getFirmwareInfo() {
|
||||||
firmwareLoading.value = true;
|
firmwareLoading.value = true;
|
||||||
try {
|
try {
|
||||||
firmware.value = await IoTOtaFirmwareApi.getOtaFirmware(firmwareId.value);
|
firmware.value = await IoTOtaFirmwareApi.getOtaFirmware(firmwareId.value);
|
||||||
} finally {
|
} finally {
|
||||||
firmwareLoading.value = false;
|
firmwareLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 获取升级统计 */
|
/** 获取升级统计 */
|
||||||
const getStatistics = async () => {
|
async function getStatistics() {
|
||||||
firmwareStatisticsLoading.value = true;
|
firmwareStatisticsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
firmwareStatistics.value =
|
firmwareStatistics.value =
|
||||||
@@ -47,7 +47,7 @@ const getStatistics = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
firmwareStatisticsLoading.value = false;
|
firmwareStatisticsLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ const firmwareStatisticsLoading = ref(false);
|
|||||||
const firmwareStatistics = ref<Record<string, number>>({});
|
const firmwareStatistics = ref<Record<string, number>>({});
|
||||||
|
|
||||||
/** 获取固件信息 */
|
/** 获取固件信息 */
|
||||||
const getFirmwareInfo = async () => {
|
async function getFirmwareInfo() {
|
||||||
firmwareLoading.value = true;
|
firmwareLoading.value = true;
|
||||||
try {
|
try {
|
||||||
firmware.value = await IoTOtaFirmwareApi.getOtaFirmware(firmwareId.value);
|
firmware.value = await IoTOtaFirmwareApi.getOtaFirmware(firmwareId.value);
|
||||||
} finally {
|
} finally {
|
||||||
firmwareLoading.value = false;
|
firmwareLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 获取升级统计 */
|
/** 获取升级统计 */
|
||||||
const getStatistics = async () => {
|
async function getStatistics() {
|
||||||
firmwareStatisticsLoading.value = true;
|
firmwareStatisticsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
firmwareStatistics.value =
|
firmwareStatistics.value =
|
||||||
@@ -47,7 +47,7 @@ const getStatistics = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
firmwareStatisticsLoading.value = false;
|
firmwareStatisticsLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const columns: TableColumnsType = [
|
|||||||
const [ModalComponent, modalApi] = useVbenModal();
|
const [ModalComponent, modalApi] = useVbenModal();
|
||||||
|
|
||||||
/** 获取任务详情 */
|
/** 获取任务详情 */
|
||||||
const getTaskInfo = async () => {
|
async function getTaskInfo() {
|
||||||
if (!taskId.value) {
|
if (!taskId.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -123,10 +123,10 @@ const getTaskInfo = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
taskLoading.value = false;
|
taskLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 获取统计数据 */
|
/** 获取统计数据 */
|
||||||
const getStatistics = async () => {
|
async function getStatistics() {
|
||||||
if (!taskId.value) {
|
if (!taskId.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -140,10 +140,10 @@ const getStatistics = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
taskStatisticsLoading.value = false;
|
taskStatisticsLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 获取升级记录列表 */
|
/** 获取升级记录列表 */
|
||||||
const getRecordList = async () => {
|
async function getRecordList() {
|
||||||
if (!taskId.value) {
|
if (!taskId.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,26 +156,26 @@ const getRecordList = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
recordLoading.value = false;
|
recordLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 切换标签 */
|
/** 切换标签 */
|
||||||
const handleTabChange = (tabKey: number | string) => {
|
function handleTabChange(tabKey: number | string) {
|
||||||
activeTab.value = String(tabKey);
|
activeTab.value = String(tabKey);
|
||||||
queryParams.pageNo = 1;
|
queryParams.pageNo = 1;
|
||||||
queryParams.status =
|
queryParams.status =
|
||||||
activeTab.value === '' ? undefined : Number.parseInt(String(tabKey));
|
activeTab.value === '' ? undefined : Number.parseInt(String(tabKey));
|
||||||
getRecordList();
|
getRecordList();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 分页变化 */
|
/** 分页变化 */
|
||||||
const handleTableChange = (pagination: any) => {
|
function handleTableChange(pagination: any) {
|
||||||
queryParams.pageNo = pagination.current;
|
queryParams.pageNo = pagination.current;
|
||||||
queryParams.pageSize = pagination.pageSize;
|
queryParams.pageSize = pagination.pageSize;
|
||||||
getRecordList();
|
getRecordList();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 取消升级 */
|
/** 取消升级 */
|
||||||
const handleCancelUpgrade = async (record: OtaTaskRecord) => {
|
async function handleCancelUpgrade(record: OtaTaskRecord) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认取消',
|
title: '确认取消',
|
||||||
content: '确认要取消该设备的升级任务吗?',
|
content: '确认要取消该设备的升级任务吗?',
|
||||||
@@ -192,10 +192,10 @@ const handleCancelUpgrade = async (record: OtaTaskRecord) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = (id: number) => {
|
function open(id: number) {
|
||||||
modalApi.open();
|
modalApi.open();
|
||||||
taskId.value = id;
|
taskId.value = id;
|
||||||
activeTab.value = '';
|
activeTab.value = '';
|
||||||
@@ -206,7 +206,7 @@ const open = (id: number) => {
|
|||||||
getTaskInfo();
|
getTaskInfo();
|
||||||
getStatistics();
|
getStatistics();
|
||||||
getRecordList();
|
getRecordList();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 暴露方法 */
|
/** 暴露方法 */
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DeviceVO } from '#/api/iot/device/device';
|
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||||
import type { OtaTask } from '#/api/iot/ota/task';
|
import type { OtaTask } from '#/api/iot/ota/task';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
@@ -57,7 +57,7 @@ const formRules = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const devices = ref<DeviceVO[]>([]);
|
const devices = ref<IotDeviceApi.Device[]>([]);
|
||||||
|
|
||||||
/** 设备选项 */
|
/** 设备选项 */
|
||||||
const deviceOptions = computed(() => {
|
const deviceOptions = computed(() => {
|
||||||
@@ -107,7 +107,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
function resetForm() {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
name: '',
|
name: '',
|
||||||
deviceScope: IoTOtaTaskDeviceScopeEnum.ALL.value,
|
deviceScope: IoTOtaTaskDeviceScopeEnum.ALL.value,
|
||||||
@@ -116,12 +116,12 @@ const resetForm = () => {
|
|||||||
deviceIds: [],
|
deviceIds: [],
|
||||||
};
|
};
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async () => {
|
async function open() {
|
||||||
await modalApi.open();
|
await modalApi.open();
|
||||||
};
|
}
|
||||||
|
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const taskFormRef = ref(); // 任务表单引用
|
|||||||
const taskDetailRef = ref(); // 任务详情引用
|
const taskDetailRef = ref(); // 任务详情引用
|
||||||
|
|
||||||
/** 获取任务列表 */
|
/** 获取任务列表 */
|
||||||
const getTaskList = async () => {
|
async function getTaskList() {
|
||||||
taskLoading.value = true;
|
taskLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const data = await IoTOtaTaskApi.getOtaTaskPage(queryParams);
|
const data = await IoTOtaTaskApi.getOtaTaskPage(queryParams);
|
||||||
@@ -49,32 +49,32 @@ const getTaskList = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
taskLoading.value = false;
|
taskLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 搜索 */
|
/** 搜索 */
|
||||||
const handleQuery = () => {
|
function handleQuery() {
|
||||||
queryParams.pageNo = 1;
|
queryParams.pageNo = 1;
|
||||||
getTaskList();
|
getTaskList();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 打开任务表单 */
|
/** 打开任务表单 */
|
||||||
const openTaskForm = () => {
|
function openTaskForm() {
|
||||||
taskFormRef.value?.open();
|
taskFormRef.value?.open();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理任务创建成功 */
|
/** 处理任务创建成功 */
|
||||||
const handleTaskCreateSuccess = () => {
|
function handleTaskCreateSuccess() {
|
||||||
getTaskList();
|
getTaskList();
|
||||||
emit('success');
|
emit('success');
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 查看任务详情 */
|
/** 查看任务详情 */
|
||||||
const handleTaskDetail = (id: number) => {
|
function handleTaskDetail(id: number) {
|
||||||
taskDetailRef.value?.open(id);
|
taskDetailRef.value?.open(id);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 取消任务 */
|
/** 取消任务 */
|
||||||
const handleCancelTask = async (id: number) => {
|
async function handleCancelTask(id: number) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认取消',
|
title: '确认取消',
|
||||||
content: '确认要取消该升级任务吗?',
|
content: '确认要取消该升级任务吗?',
|
||||||
@@ -88,20 +88,20 @@ const handleCancelTask = async (id: number) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 刷新数据 */
|
/** 刷新数据 */
|
||||||
const refresh = async () => {
|
async function refresh() {
|
||||||
await getTaskList();
|
await getTaskList();
|
||||||
emit('success');
|
emit('success');
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 分页变化 */
|
/** 分页变化 */
|
||||||
const handleTableChange = (pagination: any) => {
|
function handleTableChange(pagination: any) {
|
||||||
queryParams.pageNo = pagination.current;
|
queryParams.pageNo = pagination.current;
|
||||||
queryParams.pageSize = pagination.pageSize;
|
queryParams.pageSize = pagination.pageSize;
|
||||||
getTaskList();
|
getTaskList();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 表格列配置 */
|
/** 表格列配置 */
|
||||||
const columns: TableColumnsType = [
|
const columns: TableColumnsType = [
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
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 { IotProductCategoryApi } from '#/api/iot/product/category';
|
|
||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
import { handleTree } from '@vben/utils';
|
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import {
|
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||||
deleteProductCategory,
|
|
||||||
getProductCategoryPage,
|
|
||||||
getSimpleProductCategoryList,
|
|
||||||
} from '#/api/iot/product/category';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
/** 新增/修改产品分类的表单 */
|
/** 新增/修改产品分类的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
@@ -160,35 +151,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除分类 */
|
|
||||||
export async function handleDeleteCategory(
|
|
||||||
row: IotProductCategoryApi.ProductCategory,
|
|
||||||
onSuccess?: () => void,
|
|
||||||
) {
|
|
||||||
const hideLoading = message.loading({
|
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
|
||||||
duration: 0,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await deleteProductCategory(row.id!);
|
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
|
||||||
onSuccess?.();
|
|
||||||
} finally {
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 查询分类列表 */
|
|
||||||
export async function queryProductCategoryList({ page }: any, formValues: any) {
|
|
||||||
const data = await getProductCategoryPage({
|
|
||||||
pageNo: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
...formValues,
|
|
||||||
});
|
|
||||||
// 转换为树形结构
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
list: handleTree(data.list, 'id', 'parentId'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||||||
import type { IotProductCategoryApi } from '#/api/iot/product/category';
|
import type { IotProductCategoryApi } from '#/api/iot/product/category';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
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 {
|
||||||
|
deleteProductCategory,
|
||||||
|
getProductCategoryPage,
|
||||||
|
} from '#/api/iot/product/category';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import {
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
handleDeleteCategory,
|
|
||||||
queryProductCategoryList,
|
|
||||||
useGridColumns,
|
|
||||||
useGridFormSchema,
|
|
||||||
} from './data';
|
|
||||||
import Form from './modules/ProductCategoryForm.vue';
|
import Form from './modules/ProductCategoryForm.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProductCategory' });
|
defineOptions({ name: 'IoTProductCategory' });
|
||||||
@@ -39,7 +41,17 @@ function handleEdit(row: IotProductCategoryApi.ProductCategory) {
|
|||||||
|
|
||||||
/** 删除分类 */
|
/** 删除分类 */
|
||||||
async function handleDelete(row: IotProductCategoryApi.ProductCategory) {
|
async function handleDelete(row: IotProductCategoryApi.ProductCategory) {
|
||||||
await handleDeleteCategory(row, handleRefresh);
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteProductCategory(row.id!);
|
||||||
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
@@ -57,7 +69,18 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: queryProductCategoryList,
|
query: async ({ page }, formValues) => {
|
||||||
|
const data = await getProductCategoryPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
// 转换为树形结构
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
list: handleTree(data.list, 'id', 'parentId'),
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
|
|||||||
@@ -5,17 +5,10 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||||
import {
|
import { getProductPage } from '#/api/iot/product/product';
|
||||||
deleteProduct,
|
|
||||||
exportProduct,
|
|
||||||
getProductPage,
|
|
||||||
} from '#/api/iot/product/product';
|
|
||||||
|
|
||||||
/** 新增/修改产品的表单 */
|
/** 新增/修改产品的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
@@ -208,38 +201,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载产品分类列表 */
|
|
||||||
export async function loadCategoryList() {
|
|
||||||
return await getSimpleProductCategoryList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取分类名称 */
|
|
||||||
export function getCategoryName(categoryList: any[], categoryId: number) {
|
|
||||||
const category = categoryList.find((c: any) => c.id === categoryId);
|
|
||||||
return category?.name || '未分类';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除产品 */
|
|
||||||
export async function handleDeleteProduct(row: any, onSuccess?: () => void) {
|
|
||||||
const hideLoading = message.loading({
|
|
||||||
content: `正在删除 ${row.name}...`,
|
|
||||||
duration: 0,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await deleteProduct(row.id);
|
|
||||||
message.success(`删除 ${row.name} 成功`);
|
|
||||||
onSuccess?.();
|
|
||||||
} finally {
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 导出产品 */
|
|
||||||
export async function handleExportProduct(searchParams: any) {
|
|
||||||
const data = await exportProduct(searchParams);
|
|
||||||
downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 查询产品列表 */
|
/** 查询产品列表 */
|
||||||
export async function queryProductList({ page }: any, searchParams: any) {
|
export async function queryProductList({ page }: any, searchParams: any) {
|
||||||
return await getProductPage({
|
return await getProductPage({
|
||||||
|
|||||||
@@ -6,21 +6,20 @@ import { useRouter } from 'vue-router';
|
|||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, Card, Image, Input, Space } from 'ant-design-vue';
|
import { Button, Card, Image, Input, message, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
deleteProduct,
|
||||||
|
exportProduct,
|
||||||
|
getProductPage,
|
||||||
|
} from '#/api/crm/product';
|
||||||
|
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import {
|
import { useGridColumns, useImagePreview } from './data';
|
||||||
getCategoryName,
|
|
||||||
handleDeleteProduct,
|
|
||||||
handleExportProduct,
|
|
||||||
loadCategoryList,
|
|
||||||
queryProductList,
|
|
||||||
useGridColumns,
|
|
||||||
useImagePreview,
|
|
||||||
} from './data';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import ProductCardView from './modules/ProductCardView.vue';
|
import ProductCardView from './modules/ProductCardView.vue';
|
||||||
import ProductForm from './modules/ProductForm.vue';
|
import ProductForm from './modules/ProductForm.vue';
|
||||||
@@ -47,14 +46,15 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 加载产品分类列表
|
// 加载产品分类列表
|
||||||
const loadCategories = async () => {
|
async function loadCategories() {
|
||||||
categoryList.value = await loadCategoryList();
|
categoryList.value = await getSimpleProductCategoryList();
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取分类名称
|
// 获取分类名称
|
||||||
const getCategoryNameByValue = (categoryId: number) => {
|
function getCategoryNameByValue(categoryId: number) {
|
||||||
return getCategoryName(categoryList.value, categoryId);
|
const category = categoryList.value.find((c: any) => c.id === categoryId);
|
||||||
};
|
return category?.name || '未分类';
|
||||||
|
}
|
||||||
|
|
||||||
/** 搜索 */
|
/** 搜索 */
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
@@ -84,7 +84,8 @@ function handleRefresh() {
|
|||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function handleExport() {
|
async function handleExport() {
|
||||||
await handleExportProduct(searchParams.value);
|
const data = await exportProduct(searchParams.value);
|
||||||
|
await downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开产品详情 */
|
/** 打开产品详情 */
|
||||||
@@ -116,7 +117,17 @@ function handleEdit(row: any) {
|
|||||||
|
|
||||||
/** 删除产品 */
|
/** 删除产品 */
|
||||||
async function handleDelete(row: any) {
|
async function handleDelete(row: any) {
|
||||||
await handleDeleteProduct(row, handleRefresh);
|
const hideLoading = message.loading({
|
||||||
|
content: `正在删除 ${row.name}...`,
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteProduct(row.id);
|
||||||
|
message.success(`删除 ${row.name} 成功`);
|
||||||
|
handleRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
@@ -129,7 +140,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: ({ page }) => queryProductList({ page }, searchParams.value),
|
query: async ({ page }) => {
|
||||||
|
return await getProductPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...searchParams.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
@@ -336,6 +353,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-image-preview-operations {
|
.ant-image-preview-operations {
|
||||||
background: rgba(0, 0, 0, 0.7) !important;
|
background: rgb(0 0 0 / 70%) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ const queryParams = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 获取分类名称
|
// 获取分类名称
|
||||||
const getCategoryName = (categoryId: number) => {
|
function getCategoryName(categoryId: number) {
|
||||||
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
||||||
return category?.name || '未分类';
|
return category?.name || '未分类';
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取产品列表
|
// 获取产品列表
|
||||||
const getList = async () => {
|
async function getList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const data = await getProductPage({
|
const data = await getProductPage({
|
||||||
@@ -66,23 +66,23 @@ const getList = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 处理页码变化
|
// 处理页码变化
|
||||||
const handlePageChange = (page: number, pageSize: number) => {
|
function handlePageChange(page: number, pageSize: number) {
|
||||||
queryParams.value.pageNo = page;
|
queryParams.value.pageNo = page;
|
||||||
queryParams.value.pageSize = pageSize;
|
queryParams.value.pageSize = pageSize;
|
||||||
getList();
|
getList();
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取设备类型颜色
|
// 获取设备类型颜色
|
||||||
const getDeviceTypeColor = (deviceType: number) => {
|
function getDeviceTypeColor(deviceType: number) {
|
||||||
const colors: Record<number, string> = {
|
const colors: Record<number, string> = {
|
||||||
0: 'blue',
|
0: 'blue',
|
||||||
1: 'green',
|
1: 'green',
|
||||||
};
|
};
|
||||||
return colors[deviceType] || 'default';
|
return colors[deviceType] || 'default';
|
||||||
};
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
@@ -131,9 +131,9 @@ defineExpose({
|
|||||||
<div class="info-list flex-1">
|
<div class="info-list flex-1">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">产品分类</span>
|
<span class="info-label">产品分类</span>
|
||||||
<span class="info-value text-primary">{{
|
<span class="info-value text-primary">
|
||||||
getCategoryName(item.categoryId)
|
{{ getCategoryName(item.categoryId) }}
|
||||||
}}</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">产品类型</span>
|
<span class="info-label">产品类型</span>
|
||||||
@@ -152,9 +152,9 @@ defineExpose({
|
|||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">产品标识</span>
|
<span class="info-label">产品标识</span>
|
||||||
<Tooltip :title="item.productKey || item.id" placement="top">
|
<Tooltip :title="item.productKey || item.id" placement="top">
|
||||||
<span class="info-value product-key">{{
|
<span class="info-value product-key">
|
||||||
item.productKey || item.id
|
{{ item.productKey || item.id }}
|
||||||
}}</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,44 +236,44 @@ defineExpose({
|
|||||||
.product-card-view {
|
.product-card-view {
|
||||||
.product-card {
|
.product-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: all 0.3s ease;
|
overflow: hidden;
|
||||||
border: 1px solid #e8e8e8;
|
border: 1px solid #e8e8e8;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
||||||
border-color: #d9d9d9;
|
border-color: #d9d9d9;
|
||||||
|
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.ant-card-body) {
|
:deep(.ant-card-body) {
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 产品图标
|
// 产品图标
|
||||||
.product-icon {
|
.product-icon {
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
color: white;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: white;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 产品标题
|
// 产品标题
|
||||||
.product-title {
|
.product-title {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
line-height: 1.5;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #1f2937;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,16 +290,16 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
color: #6b7280;
|
|
||||||
margin-right: 8px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
color: #1f2937;
|
|
||||||
font-weight: 500;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1f2937;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&.text-primary {
|
&.text-primary {
|
||||||
@@ -308,15 +308,15 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-key {
|
.product-key {
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #374151;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
color: #374151;
|
||||||
|
white-space: nowrap;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,15 +328,15 @@ defineExpose({
|
|||||||
|
|
||||||
// 3D 图标
|
// 3D 图标
|
||||||
.product-3d-icon {
|
.product-3d-icon {
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
color: #667eea;
|
||||||
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
|
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
flex-shrink: 0;
|
|
||||||
color: #667eea;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按钮组
|
// 按钮组
|
||||||
@@ -344,8 +344,8 @@ defineExpose({
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -359,8 +359,8 @@ defineExpose({
|
|||||||
border-color: #1890ff;
|
border-color: #1890ff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #1890ff;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
background: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,8 +369,8 @@ defineExpose({
|
|||||||
border-color: #52c41a;
|
border-color: #52c41a;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #52c41a;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
background: #52c41a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,8 +379,8 @@ defineExpose({
|
|||||||
border-color: #722ed1;
|
border-color: #722ed1;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #722ed1;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
background: #722ed1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
|||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getProductPage } from '#/api/iot/product/product';
|
import { getProductPage } from '#/api/iot/product/product';
|
||||||
@@ -101,24 +102,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 打开选择器
|
// 打开选择器
|
||||||
const open = async () => {
|
async function open() {
|
||||||
selectedProducts.value = [];
|
selectedProducts.value = [];
|
||||||
selectedRowKeys.value = [];
|
selectedRowKeys.value = [];
|
||||||
modalApi.open();
|
modalApi.open();
|
||||||
gridApi.reload();
|
gridApi.reload();
|
||||||
};
|
}
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
function handleSearch() {
|
||||||
gridApi.reload();
|
gridApi.reload();
|
||||||
};
|
}
|
||||||
|
|
||||||
// 重置搜索
|
// 重置搜索
|
||||||
const handleReset = () => {
|
function handleReset() {
|
||||||
queryParams.name = '';
|
queryParams.name = '';
|
||||||
queryParams.productKey = '';
|
queryParams.productKey = '';
|
||||||
gridApi.reload();
|
gridApi.reload();
|
||||||
};
|
}
|
||||||
|
|
||||||
// 确认选择
|
// 确认选择
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
@@ -151,40 +152,40 @@ defineExpose({ open });
|
|||||||
<template>
|
<template>
|
||||||
<Modal class="!w-[900px]">
|
<Modal class="!w-[900px]">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<a-form layout="inline" :model="queryParams">
|
<Form layout="inline" :model="queryParams">
|
||||||
<a-form-item label="产品名称">
|
<Form.Item label="产品名称">
|
||||||
<a-input
|
<Input
|
||||||
v-model:value="queryParams.name"
|
v-model:value="queryParams.name"
|
||||||
placeholder="请输入产品名称"
|
placeholder="请输入产品名称"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="!w-[200px]"
|
class="!w-[200px]"
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
<a-form-item label="ProductKey">
|
<Form.Item label="ProductKey">
|
||||||
<a-input
|
<Input
|
||||||
v-model:value="queryParams.productKey"
|
v-model:value="queryParams.productKey"
|
||||||
placeholder="请输入产品标识"
|
placeholder="请输入产品标识"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="!w-[200px]"
|
class="!w-[200px]"
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
<a-form-item>
|
<Form.Item>
|
||||||
<a-button type="primary" @click="handleSearch">
|
<Button type="primary" @click="handleSearch">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Icon icon="ant-design:search-outlined" />
|
<IconifyIcon icon="ant-design:search-outlined" />
|
||||||
</template>
|
</template>
|
||||||
搜索
|
搜索
|
||||||
</a-button>
|
</Button>
|
||||||
<a-button class="ml-2" @click="handleReset">
|
<Button class="ml-2" @click="handleReset">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Icon icon="ant-design:reload-outlined" />
|
<IconifyIcon icon="ant-design:reload-outlined" />
|
||||||
</template>
|
</template>
|
||||||
重置
|
重置
|
||||||
</a-button>
|
</Button>
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
</a-form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Grid />
|
<Grid />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { Button, Card, Descriptions, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { updateProductStatus } from '#/api/iot/product/product';
|
import { updateProductStatus } from '#/api/iot/product/product';
|
||||||
|
|
||||||
@@ -27,30 +27,30 @@ const router = useRouter();
|
|||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
|
|
||||||
/** 复制到剪贴板 */
|
/** 复制到剪贴板 */
|
||||||
const copyToClipboard = async (text: string) => {
|
async function copyToClipboard(text: string) {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
message.success('复制成功');
|
message.success('复制成功');
|
||||||
} catch {
|
} catch {
|
||||||
message.error('复制失败');
|
message.error('复制失败');
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 跳转到设备管理 */
|
/** 跳转到设备管理 */
|
||||||
const goToDeviceList = (productId: number) => {
|
function goToDeviceList(productId: number) {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/iot/device/device',
|
path: '/iot/device/device',
|
||||||
query: { productId: String(productId) },
|
query: { productId: String(productId) },
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 打开编辑表单 */
|
/** 打开编辑表单 */
|
||||||
const openForm = (type: string, id?: number) => {
|
function openForm(type: string, id?: number) {
|
||||||
formRef.value?.open(type, id);
|
formRef.value?.open(type, id);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 发布产品 */
|
/** 发布产品 */
|
||||||
const confirmPublish = async (id: number) => {
|
async function confirmPublish(id: number) {
|
||||||
try {
|
try {
|
||||||
await updateProductStatus(id, 1);
|
await updateProductStatus(id, 1);
|
||||||
message.success('发布成功');
|
message.success('发布成功');
|
||||||
@@ -58,10 +58,10 @@ const confirmPublish = async (id: number) => {
|
|||||||
} catch {
|
} catch {
|
||||||
message.error('发布失败');
|
message.error('发布失败');
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 撤销发布 */
|
/** 撤销发布 */
|
||||||
const confirmUnpublish = async (id: number) => {
|
async function confirmUnpublish(id: number) {
|
||||||
try {
|
try {
|
||||||
await updateProductStatus(id, 0);
|
await updateProductStatus(id, 0);
|
||||||
message.success('撤销发布成功');
|
message.success('撤销发布成功');
|
||||||
@@ -69,7 +69,7 @@ const confirmUnpublish = async (id: number) => {
|
|||||||
} catch {
|
} catch {
|
||||||
message.error('撤销发布失败');
|
message.error('撤销发布失败');
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -79,51 +79,51 @@ const confirmUnpublish = async (id: number) => {
|
|||||||
<h2 class="text-xl font-bold">{{ product.name }}</h2>
|
<h2 class="text-xl font-bold">{{ product.name }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-x-2">
|
<div class="space-x-2">
|
||||||
<a-button
|
<Button
|
||||||
:disabled="product.status === 1"
|
:disabled="product.status === 1"
|
||||||
@click="openForm('update', product.id)"
|
@click="openForm('update', product.id)"
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</a-button>
|
</Button>
|
||||||
<a-button
|
<Button
|
||||||
v-if="product.status === 0"
|
v-if="product.status === 0"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="confirmPublish(product.id!)"
|
@click="confirmPublish(product.id!)"
|
||||||
>
|
>
|
||||||
发布
|
发布
|
||||||
</a-button>
|
</Button>
|
||||||
<a-button
|
<Button
|
||||||
v-if="product.status === 1"
|
v-if="product.status === 1"
|
||||||
danger
|
danger
|
||||||
@click="confirmUnpublish(product.id!)"
|
@click="confirmUnpublish(product.id!)"
|
||||||
>
|
>
|
||||||
撤销发布
|
撤销发布
|
||||||
</a-button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-card class="mt-4">
|
<Card class="mt-4">
|
||||||
<a-descriptions :column="1">
|
<Descriptions :column="1">
|
||||||
<a-descriptions-item label="ProductKey">
|
<Descriptions.Item label="ProductKey">
|
||||||
{{ product.productKey }}
|
{{ product.productKey }}
|
||||||
<a-button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
class="ml-2"
|
class="ml-2"
|
||||||
@click="copyToClipboard(product.productKey || '')"
|
@click="copyToClipboard(product.productKey || '')"
|
||||||
>
|
>
|
||||||
复制
|
复制
|
||||||
</a-button>
|
</Button>
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="设备总数">
|
<Descriptions.Item label="设备总数">
|
||||||
<span class="ml-5 mr-2">{{
|
<span class="ml-5 mr-2">
|
||||||
product.deviceCount ?? '加载中...'
|
{{ product.deviceCount ?? '加载中...' }}
|
||||||
}}</span>
|
</span>
|
||||||
<a-button size="small" @click="goToDeviceList(product.id!)">
|
<Button size="small" @click="goToDeviceList(product.id!)">
|
||||||
前往管理
|
前往管理
|
||||||
</a-button>
|
</Button>
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
</a-descriptions>
|
</Descriptions>
|
||||||
</a-card>
|
</Card>
|
||||||
|
|
||||||
<!-- 表单弹窗 -->
|
<!-- 表单弹窗 -->
|
||||||
<ProductForm ref="formRef" @success="emit('refresh')" />
|
<ProductForm ref="formRef" @success="emit('refresh')" />
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
|||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
|
||||||
|
import { Card, Descriptions } from 'ant-design-vue';
|
||||||
|
|
||||||
import { DeviceTypeEnum } from '#/api/iot/product/product';
|
import { DeviceTypeEnum } from '#/api/iot/product/product';
|
||||||
import { DictTag } from '#/components/dict-tag';
|
import { DictTag } from '#/components/dict-tag';
|
||||||
|
|
||||||
@@ -20,33 +22,33 @@ const formatDate = (date?: Date | string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-card title="产品信息">
|
<Card title="产品信息">
|
||||||
<a-descriptions bordered :column="3">
|
<Descriptions bordered :column="3">
|
||||||
<a-descriptions-item label="产品名称">
|
<Descriptions.Item label="产品名称">
|
||||||
{{ product.name }}
|
{{ product.name }}
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="所属分类">
|
<Descriptions.Item label="所属分类">
|
||||||
{{ product.categoryName || '-' }}
|
{{ product.categoryName || '-' }}
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="设备类型">
|
<Descriptions.Item label="设备类型">
|
||||||
<DictTag
|
<DictTag
|
||||||
:type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
|
:type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
|
||||||
:value="product.deviceType"
|
:value="product.deviceType"
|
||||||
/>
|
/>
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="定位类型">
|
<Descriptions.Item label="定位类型">
|
||||||
{{ product.locationType ?? '-' }}
|
{{ product.locationType ?? '-' }}
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="创建时间">
|
<Descriptions.Item label="创建时间">
|
||||||
{{ formatDate(product.createTime) }}
|
{{ formatDate(product.createTime) }}
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="数据格式">
|
<Descriptions.Item label="数据格式">
|
||||||
{{ product.codecType || '-' }}
|
{{ product.codecType || '-' }}
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="产品状态">
|
<Descriptions.Item label="产品状态">
|
||||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
|
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item
|
<Descriptions.Item
|
||||||
v-if="
|
v-if="
|
||||||
[DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY].includes(
|
[DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY].includes(
|
||||||
product.deviceType!,
|
product.deviceType!,
|
||||||
@@ -55,10 +57,10 @@ const formatDate = (date?: Date | string) => {
|
|||||||
label="联网方式"
|
label="联网方式"
|
||||||
>
|
>
|
||||||
<DictTag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
|
<DictTag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
<a-descriptions-item label="产品描述" :span="3">
|
<Descriptions.Item label="产品描述" :span="3">
|
||||||
{{ product.description || '-' }}
|
{{ product.description || '-' }}
|
||||||
</a-descriptions-item>
|
</Descriptions.Item>
|
||||||
</a-descriptions>
|
</Descriptions>
|
||||||
</a-card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useRoute, useRouter } from 'vue-router';
|
|||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getDeviceCount } from '#/api/iot/device/device';
|
import { getDeviceCount } from '#/api/iot/device/device';
|
||||||
import { getProduct } from '#/api/iot/product/product';
|
import { getProduct } from '#/api/iot/product/product';
|
||||||
@@ -29,7 +29,7 @@ const activeTab = ref('info');
|
|||||||
provide('product', product);
|
provide('product', product);
|
||||||
|
|
||||||
/** 获取产品详情 */
|
/** 获取产品详情 */
|
||||||
const getProductData = async (productId: number) => {
|
async function getProductData(productId: number) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
product.value = await getProduct(productId);
|
product.value = await getProduct(productId);
|
||||||
@@ -38,10 +38,10 @@ const getProductData = async (productId: number) => {
|
|||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 查询设备数量 */
|
/** 查询设备数量 */
|
||||||
const getDeviceCountData = async (productId: number) => {
|
async function getDeviceCountData(productId: number) {
|
||||||
try {
|
try {
|
||||||
return await getDeviceCount(productId);
|
return await getDeviceCount(productId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -53,7 +53,7 @@ const getDeviceCountData = async (productId: number) => {
|
|||||||
);
|
);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -86,16 +86,16 @@ onMounted(async () => {
|
|||||||
@refresh="() => getProductData(id)"
|
@refresh="() => getProductData(id)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<a-tabs v-model:active-key="activeTab" class="mt-4">
|
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||||
<a-tab-pane key="info" tab="产品信息">
|
<Tabs.TabPane key="info" tab="产品信息">
|
||||||
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
||||||
</a-tab-pane>
|
</Tabs.TabPane>
|
||||||
<a-tab-pane key="thingModel" tab="物模型(功能定义)">
|
<Tabs.TabPane key="thingModel" tab="物模型(功能定义)">
|
||||||
<IoTProductThingModel
|
<IoTProductThingModel
|
||||||
v-if="activeTab === 'thingModel'"
|
v-if="activeTab === 'thingModel'"
|
||||||
:product-id="id"
|
:product-id="id"
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</Tabs.TabPane>
|
||||||
</a-tabs>
|
</Tabs>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
|
|
||||||
import { Button, Form, FormItem, Select, Table } from 'ant-design-vue';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, Form, Select, Table } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getSimpleDeviceList } from '#/api/iot/device/device';
|
import { getSimpleDeviceList } from '#/api/iot/device/device';
|
||||||
import { getSimpleProductList } from '#/api/iot/product/product';
|
import { getSimpleProductList } from '#/api/iot/product/product';
|
||||||
@@ -31,23 +33,23 @@ const upstreamMethods = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 根据产品 ID 过滤设备 */
|
/** 根据产品 ID 过滤设备 */
|
||||||
const getFilteredDevices = (productId: number) => {
|
function getFilteredDevices(productId: number) {
|
||||||
if (!productId) return [];
|
if (!productId) return [];
|
||||||
return deviceList.value.filter(
|
return deviceList.value.filter(
|
||||||
(device: any) => device.productId === productId,
|
(device: any) => device.productId === productId,
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 判断是否需要显示标识符选择器 */
|
/** 判断是否需要显示标识符选择器 */
|
||||||
const shouldShowIdentifierSelect = (row: any) => {
|
function shouldShowIdentifierSelect(row: any) {
|
||||||
return [
|
return [
|
||||||
IotDeviceMessageMethodEnum.EVENT_POST.method,
|
IotDeviceMessageMethodEnum.EVENT_POST.method,
|
||||||
IotDeviceMessageMethodEnum.PROPERTY_POST.method,
|
IotDeviceMessageMethodEnum.PROPERTY_POST.method,
|
||||||
].includes(row.method);
|
].includes(row.method);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 获取物模型选项 */
|
/** 获取物模型选项 */
|
||||||
const getThingModelOptions = (row: any) => {
|
function getThingModelOptions(row: any) {
|
||||||
if (!row.productId || !shouldShowIdentifierSelect(row)) {
|
if (!row.productId || !shouldShowIdentifierSelect(row)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -66,28 +68,28 @@ const getThingModelOptions = (row: any) => {
|
|||||||
label: `${item.name} (${item.identifier})`,
|
label: `${item.name} (${item.identifier})`,
|
||||||
value: item.identifier,
|
value: item.identifier,
|
||||||
}));
|
}));
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 加载产品列表 */
|
/** 加载产品列表 */
|
||||||
const loadProductList = async () => {
|
async function loadProductList() {
|
||||||
try {
|
try {
|
||||||
productList.value = await getSimpleProductList();
|
productList.value = await getSimpleProductList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载产品列表失败:', error);
|
console.error('加载产品列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 加载设备列表 */
|
/** 加载设备列表 */
|
||||||
const loadDeviceList = async () => {
|
async function loadDeviceList() {
|
||||||
try {
|
try {
|
||||||
deviceList.value = await getSimpleDeviceList();
|
deviceList.value = await getSimpleDeviceList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载设备列表失败:', error);
|
console.error('加载设备列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 加载物模型数据 */
|
/** 加载物模型数据 */
|
||||||
const loadThingModel = async (productId: number) => {
|
async function loadThingModel(productId: number) {
|
||||||
// 已缓存,无需重复加载
|
// 已缓存,无需重复加载
|
||||||
if (thingModelCache.value.has(productId)) {
|
if (thingModelCache.value.has(productId)) {
|
||||||
return;
|
return;
|
||||||
@@ -98,18 +100,18 @@ const loadThingModel = async (productId: number) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载物模型失败:', error);
|
console.error('加载物模型失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 产品变化时处理 */
|
/** 产品变化时处理 */
|
||||||
const handleProductChange = async (row: any, _index: number) => {
|
async function handleProductChange(row: any, _index: number) {
|
||||||
row.deviceId = 0;
|
row.deviceId = 0;
|
||||||
row.method = undefined;
|
row.method = undefined;
|
||||||
row.identifier = undefined;
|
row.identifier = undefined;
|
||||||
row.identifierLoading = false;
|
row.identifierLoading = false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 消息方法变化时处理 */
|
/** 消息方法变化时处理 */
|
||||||
const handleMethodChange = async (row: any, _index: number) => {
|
async function handleMethodChange(row: any, _index: number) {
|
||||||
// 清空标识符
|
// 清空标识符
|
||||||
row.identifier = undefined;
|
row.identifier = undefined;
|
||||||
// 如果需要加载物模型数据
|
// 如果需要加载物模型数据
|
||||||
@@ -118,10 +120,10 @@ const handleMethodChange = async (row: any, _index: number) => {
|
|||||||
await loadThingModel(row.productId);
|
await loadThingModel(row.productId);
|
||||||
row.identifierLoading = false;
|
row.identifierLoading = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = () => {
|
function handleAdd() {
|
||||||
const row = {
|
const row = {
|
||||||
productId: undefined,
|
productId: undefined,
|
||||||
deviceId: undefined,
|
deviceId: undefined,
|
||||||
@@ -130,25 +132,25 @@ const handleAdd = () => {
|
|||||||
identifierLoading: false,
|
identifierLoading: false,
|
||||||
};
|
};
|
||||||
formData.value.push(row);
|
formData.value.push(row);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = (index: number) => {
|
function handleDelete(index: number) {
|
||||||
formData.value.splice(index, 1);
|
formData.value.splice(index, 1);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 表单校验 */
|
/** 表单校验 */
|
||||||
const validate = () => {
|
function validate() {
|
||||||
return formRef.value.validate();
|
return formRef.value.validate();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 表单值 */
|
/** 表单值 */
|
||||||
const getData = () => {
|
function getData() {
|
||||||
return formData.value;
|
return formData.value;
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 设置表单值 */
|
/** 设置表单值 */
|
||||||
const setData = (data: any[]) => {
|
function setData(data: any[]) {
|
||||||
// 确保每个项都有必要的字段
|
// 确保每个项都有必要的字段
|
||||||
formData.value = (data || []).map((item) => ({
|
formData.value = (data || []).map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -160,7 +162,7 @@ const setData = (data: any[]) => {
|
|||||||
await loadThingModel(item.productId);
|
await loadThingModel(item.productId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -209,7 +211,7 @@ defineExpose({ validate, getData, setData });
|
|||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record, index }">
|
<template #bodyCell="{ column, record, index }">
|
||||||
<template v-if="column.dataIndex === 'productId'">
|
<template v-if="column.dataIndex === 'productId'">
|
||||||
<FormItem
|
<Form.Item
|
||||||
:name="['data', index, 'productId']"
|
:name="['data', index, 'productId']"
|
||||||
:rules="formRules.productId"
|
:rules="formRules.productId"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
@@ -227,10 +229,10 @@ defineExpose({ validate, getData, setData });
|
|||||||
"
|
"
|
||||||
@change="() => handleProductChange(record, index)"
|
@change="() => handleProductChange(record, index)"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'deviceId'">
|
<template v-else-if="column.dataIndex === 'deviceId'">
|
||||||
<FormItem
|
<Form.Item
|
||||||
:name="['data', index, 'deviceId']"
|
:name="['data', index, 'deviceId']"
|
||||||
:rules="formRules.deviceId"
|
:rules="formRules.deviceId"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
@@ -251,10 +253,10 @@ defineExpose({ validate, getData, setData });
|
|||||||
})),
|
})),
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'method'">
|
<template v-else-if="column.dataIndex === 'method'">
|
||||||
<FormItem
|
<Form.Item
|
||||||
:name="['data', index, 'method']"
|
:name="['data', index, 'method']"
|
||||||
:rules="formRules.method"
|
:rules="formRules.method"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
@@ -275,10 +277,10 @@ defineExpose({ validate, getData, setData });
|
|||||||
"
|
"
|
||||||
@change="() => handleMethodChange(record, index)"
|
@change="() => handleMethodChange(record, index)"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'identifier'">
|
<template v-else-if="column.dataIndex === 'identifier'">
|
||||||
<FormItem :name="['data', index, 'identifier']" class="mb-0">
|
<Form.Item :name="['data', index, 'identifier']" class="mb-0">
|
||||||
<Select
|
<Select
|
||||||
v-if="shouldShowIdentifierSelect(record)"
|
v-if="shouldShowIdentifierSelect(record)"
|
||||||
v-model:value="record.identifier"
|
v-model:value="record.identifier"
|
||||||
@@ -291,7 +293,7 @@ defineExpose({ validate, getData, setData });
|
|||||||
"
|
"
|
||||||
:options="getThingModelOptions(record)"
|
:options="getThingModelOptions(record)"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.title === '操作'">
|
<template v-else-if="column.title === '操作'">
|
||||||
<Button type="link" danger @click="handleDelete(index)">删除</Button>
|
<Button type="link" danger @click="handleDelete(index)">删除</Button>
|
||||||
@@ -300,7 +302,7 @@ defineExpose({ validate, getData, setData });
|
|||||||
</Table>
|
</Table>
|
||||||
<div class="mt-3 text-center">
|
<div class="mt-3 text-center">
|
||||||
<Button type="primary" @click="handleAdd">
|
<Button type="primary" @click="handleAdd">
|
||||||
<Icon icon="ant-design:plus-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
|
||||||
添加数据源
|
添加数据源
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
createDataSink,
|
createDataSink,
|
||||||
getDataSink,
|
getDataSink,
|
||||||
updateDataSink,
|
updateDataSink,
|
||||||
} from '#/api/iot/rule/data';
|
} from '#/api/iot/rule/data/sink';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -106,7 +106,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
// 监听类型变化,重置配置
|
// 监听类型变化,重置配置
|
||||||
watch(
|
watch(
|
||||||
() => formApi.form.type,
|
() => formApi.getValues().then((values) => values.type),
|
||||||
(newType) => {
|
(newType) => {
|
||||||
if (formData.value && newType !== formData.value.type) {
|
if (formData.value && newType !== formData.value.type) {
|
||||||
formData.value.config = {};
|
formData.value.config = {};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
@@ -12,7 +14,7 @@ const props = defineProps<{
|
|||||||
modelValue: any;
|
modelValue: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const config = useVModel(props, 'modelValue', emit) as Ref<any>;
|
const config = useVModel(props, 'modelValue', emit) as any;
|
||||||
|
|
||||||
// noinspection HttpUrlsUsage
|
// noinspection HttpUrlsUsage
|
||||||
/** URL处理 */
|
/** URL处理 */
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
@@ -10,7 +12,7 @@ const props = defineProps<{
|
|||||||
modelValue: any;
|
modelValue: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const config = useVModel(props, 'modelValue', emit) as Ref<any>;
|
const config = useVModel(props, 'modelValue', emit) as any;
|
||||||
|
|
||||||
/** 组件初始化 */
|
/** 组件初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
@@ -10,7 +12,7 @@ const props = defineProps<{
|
|||||||
modelValue: any;
|
modelValue: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const config = useVModel(props, 'modelValue', emit) as Ref<any>;
|
const config = useVModel(props, 'modelValue', emit) as any;
|
||||||
|
|
||||||
/** 组件初始化 */
|
/** 组件初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
@@ -10,7 +12,7 @@ const props = defineProps<{
|
|||||||
modelValue: any;
|
modelValue: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const config = useVModel(props, 'modelValue', emit) as Ref<any>;
|
const config = useVModel(props, 'modelValue', emit) as any;
|
||||||
|
|
||||||
/** 组件初始化 */
|
/** 组件初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
@@ -10,7 +12,7 @@ const props = defineProps<{
|
|||||||
modelValue: any;
|
modelValue: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const config = useVModel(props, 'modelValue', emit) as Ref<any>;
|
const config = useVModel(props, 'modelValue', emit) as any;
|
||||||
|
|
||||||
/** 组件初始化 */
|
/** 组件初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
@@ -10,7 +12,7 @@ const props = defineProps<{
|
|||||||
modelValue: any;
|
modelValue: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const config = useVModel(props, 'modelValue', emit) as Ref<any>;
|
const config = useVModel(props, 'modelValue', emit) as any;
|
||||||
|
|
||||||
/** 组件初始化 */
|
/** 组件初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { Delete, Plus } from '@element-plus/icons-vue';
|
import { Button, Input } from 'ant-design-vue';
|
||||||
|
|
||||||
defineOptions({ name: 'KeyValueEditor' });
|
defineOptions({ name: 'KeyValueEditor' });
|
||||||
|
|
||||||
@@ -20,19 +23,19 @@ interface KeyValueItem {
|
|||||||
const items = ref<KeyValueItem[]>([]); // 内部 key-value 项列表
|
const items = ref<KeyValueItem[]>([]); // 内部 key-value 项列表
|
||||||
|
|
||||||
/** 添加项目 */
|
/** 添加项目 */
|
||||||
const addItem = () => {
|
function addItem() {
|
||||||
items.value.push({ key: '', value: '' });
|
items.value.push({ key: '', value: '' });
|
||||||
updateModelValue();
|
updateModelValue();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 移除项目 */
|
/** 移除项目 */
|
||||||
const removeItem = (index: number) => {
|
function removeItem(index: number) {
|
||||||
items.value.splice(index, 1);
|
items.value.splice(index, 1);
|
||||||
updateModelValue();
|
updateModelValue();
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 更新 modelValue */
|
/** 更新 modelValue */
|
||||||
const updateModelValue = () => {
|
function updateModelValue() {
|
||||||
const result: Record<string, string> = {};
|
const result: Record<string, string> = {};
|
||||||
items.value.forEach((item) => {
|
items.value.forEach((item) => {
|
||||||
if (item.key) {
|
if (item.key) {
|
||||||
@@ -40,7 +43,7 @@ const updateModelValue = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
emit('update:modelValue', result);
|
emit('update:modelValue', result);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 监听项目变化 */
|
/** 监听项目变化 */
|
||||||
watch(items, updateModelValue, { deep: true });
|
watch(items, updateModelValue, { deep: true });
|
||||||
@@ -61,19 +64,15 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-for="(item, index) in items" :key="index" class="mb-2 flex w-full">
|
<div v-for="(item, index) in items" :key="index" class="mb-2 flex w-full">
|
||||||
<el-input v-model="item.key" class="mr-2" placeholder="键" />
|
<Input v-model="item.key" class="mr-2" placeholder="键" />
|
||||||
<el-input v-model="item.value" placeholder="值" />
|
<Input v-model="item.value" placeholder="值" />
|
||||||
<el-button class="ml-2" text type="danger" @click="removeItem(index)">
|
<Button class="ml-2" text danger @click="removeItem(index)">
|
||||||
<el-icon>
|
<IconifyIcon icon="ant-design:delete-outlined" />
|
||||||
<Delete />
|
|
||||||
</el-icon>
|
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<el-button text type="primary" @click="addItem">
|
<Button text type="primary" @click="addItem">
|
||||||
<el-icon>
|
<IconifyIcon icon="ant-design:plus-outlined" />
|
||||||
<Plus />
|
|
||||||
</el-icon>
|
|
||||||
{{ addButtonText }}
|
{{ addButtonText }}
|
||||||
</el-button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<!-- 告警配置组件 -->
|
<!-- 告警配置组件 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { AlertConfigApi } from '#/api/iot/alert/config';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { Form, Select, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getAlertConfigPage } from '#/api/iot/alert/config';
|
||||||
|
|
||||||
/** 告警配置组件 */
|
/** 告警配置组件 */
|
||||||
defineOptions({ name: 'AlertConfig' });
|
defineOptions({ name: 'AlertConfig' });
|
||||||
@@ -24,17 +27,17 @@ const alertConfigs = ref<any[]>([]); // 告警配置列表
|
|||||||
* 处理选择变化事件
|
* 处理选择变化事件
|
||||||
* @param value 选中的值
|
* @param value 选中的值
|
||||||
*/
|
*/
|
||||||
const handleChange = (value?: number) => {
|
function handleChange(value?: any) {
|
||||||
emit('update:modelValue', value);
|
emit('update:modelValue', value);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载告警配置列表
|
* 加载告警配置列表
|
||||||
*/
|
*/
|
||||||
const loadAlertConfigs = async () => {
|
async function loadAlertConfigs() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const data = await AlertConfigApi.getAlertConfigPage({
|
const data = await getAlertConfigPage({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
enabled: true, // 只加载启用的配置
|
enabled: true, // 只加载启用的配置
|
||||||
@@ -43,7 +46,7 @@ const loadAlertConfigs = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 组件挂载时加载数据
|
// 组件挂载时加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -53,8 +56,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<el-form-item label="告警配置" required>
|
<Form.Item label="告警配置" required>
|
||||||
<el-select
|
<Select
|
||||||
v-model="localValue"
|
v-model="localValue"
|
||||||
placeholder="请选择告警配置"
|
placeholder="请选择告警配置"
|
||||||
filterable
|
filterable
|
||||||
@@ -63,7 +66,7 @@ onMounted(() => {
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
>
|
>
|
||||||
<el-option
|
<Select.Option
|
||||||
v-for="config in alertConfigs"
|
v-for="config in alertConfigs"
|
||||||
:key="config.id"
|
:key="config.id"
|
||||||
:label="config.name"
|
:label="config.name"
|
||||||
@@ -71,12 +74,12 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span>{{ config.name }}</span>
|
<span>{{ config.name }}</span>
|
||||||
<el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
|
<Tag :type="config.enabled ? 'success' : 'danger'" size="small">
|
||||||
{{ config.enabled ? '启用' : '禁用' }}
|
{{ config.enabled ? '启用' : '禁用' }}
|
||||||
</el-tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</Select.Option>
|
||||||
</el-select>
|
</Select>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TriggerCondition } from '#/api/iot/rule/scene';
|
import type { TriggerCondition } from '#/api/iot/rule/scene';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { Col, Form, Row, Select } from 'ant-design-vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getConditionTypeOptions,
|
getConditionTypeOptions,
|
||||||
@@ -61,9 +64,9 @@ const propertyConfig = ref<any>(null); // 属性配置
|
|||||||
const isDeviceCondition = computed(() => {
|
const isDeviceCondition = computed(() => {
|
||||||
return (
|
return (
|
||||||
condition.value.type ===
|
condition.value.type ===
|
||||||
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS ||
|
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS.toString() ||
|
||||||
condition.value.type ===
|
condition.value.type ===
|
||||||
IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
|
IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY.toString()
|
||||||
);
|
);
|
||||||
}); // 计算属性:判断是否为设备相关条件
|
}); // 计算属性:判断是否为设备相关条件
|
||||||
|
|
||||||
@@ -72,25 +75,25 @@ const isDeviceCondition = computed(() => {
|
|||||||
* @param field 字段名
|
* @param field 字段名
|
||||||
* @param value 字段值
|
* @param value 字段值
|
||||||
*/
|
*/
|
||||||
const updateConditionField = (field: any, value: any) => {
|
function updateConditionField(field: any, value: any) {
|
||||||
(condition.value as any)[field] = value;
|
(condition.value as any)[field] = value;
|
||||||
emit('update:modelValue', condition.value);
|
emit('update:modelValue', condition.value);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新整个条件对象
|
* 更新整个条件对象
|
||||||
* @param newCondition 新的条件对象
|
* @param newCondition 新的条件对象
|
||||||
*/
|
*/
|
||||||
const updateCondition = (newCondition: TriggerCondition) => {
|
function updateCondition(newCondition: TriggerCondition) {
|
||||||
condition.value = newCondition;
|
condition.value = newCondition;
|
||||||
emit('update:modelValue', condition.value);
|
emit('update:modelValue', condition.value);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理条件类型变化事件
|
* 处理条件类型变化事件
|
||||||
* @param type 条件类型
|
* @param type 条件类型
|
||||||
*/
|
*/
|
||||||
const handleConditionTypeChange = (type: number) => {
|
function handleConditionTypeChange(type: any) {
|
||||||
// 根据条件类型清理字段
|
// 根据条件类型清理字段
|
||||||
const isCurrentTime =
|
const isCurrentTime =
|
||||||
type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME;
|
type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME;
|
||||||
@@ -115,26 +118,26 @@ const handleConditionTypeChange = (type: number) => {
|
|||||||
|
|
||||||
// 清空参数值
|
// 清空参数值
|
||||||
condition.value.param = '';
|
condition.value.param = '';
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理产品变化事件 */
|
/** 处理产品变化事件 */
|
||||||
const handleProductChange = (_: number) => {
|
function handleProductChange(_: any) {
|
||||||
// 产品变化时清空设备和属性
|
// 产品变化时清空设备和属性
|
||||||
condition.value.deviceId = undefined;
|
condition.value.deviceId = undefined;
|
||||||
condition.value.identifier = '';
|
condition.value.identifier = '';
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理设备变化事件 */
|
/** 处理设备变化事件 */
|
||||||
const handleDeviceChange = (_: number) => {
|
function handleDeviceChange(_: any) {
|
||||||
// 设备变化时清空属性
|
// 设备变化时清空属性
|
||||||
condition.value.identifier = '';
|
condition.value.identifier = '';
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理属性变化事件
|
* 处理属性变化事件
|
||||||
* @param propertyInfo 属性信息对象
|
* @param propertyInfo 属性信息对象
|
||||||
*/
|
*/
|
||||||
const handlePropertyChange = (propertyInfo: { config: any; type: string }) => {
|
function handlePropertyChange(propertyInfo: { config: any; type: string }) {
|
||||||
propertyType.value = propertyInfo.type;
|
propertyType.value = propertyInfo.type;
|
||||||
propertyConfig.value = propertyInfo.config;
|
propertyConfig.value = propertyInfo.config;
|
||||||
|
|
||||||
@@ -142,43 +145,45 @@ const handlePropertyChange = (propertyInfo: { config: any; type: string }) => {
|
|||||||
condition.value.operator =
|
condition.value.operator =
|
||||||
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value;
|
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value;
|
||||||
condition.value.param = '';
|
condition.value.param = '';
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理操作符变化事件 */
|
/** 处理操作符变化事件 */
|
||||||
const handleOperatorChange = () => {
|
function handleOperatorChange() {
|
||||||
// 重置值
|
// 重置值
|
||||||
condition.value.param = '';
|
condition.value.param = '';
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="gap-16px flex flex-col">
|
<div class="gap-16px flex flex-col">
|
||||||
<!-- 条件类型选择 -->
|
<!-- 条件类型选择 -->
|
||||||
<el-row :gutter="16">
|
<Row :gutter="16">
|
||||||
<el-col :span="8">
|
<Col :span="8">
|
||||||
<el-form-item label="条件类型" required>
|
<Form.Item label="条件类型" required>
|
||||||
<el-select
|
<Select
|
||||||
:model-value="condition.type"
|
:model-value="condition.type"
|
||||||
@update:model-value="(value) => updateConditionField('type', value)"
|
@update:model-value="
|
||||||
|
(value: any) => updateConditionField('type', value)
|
||||||
|
"
|
||||||
@change="handleConditionTypeChange"
|
@change="handleConditionTypeChange"
|
||||||
placeholder="请选择条件类型"
|
placeholder="请选择条件类型"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
>
|
>
|
||||||
<el-option
|
<Select.Option
|
||||||
v-for="option in getConditionTypeOptions()"
|
v-for="option in getConditionTypeOptions()"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:label="option.label"
|
:label="option.label"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</Select>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
</el-row>
|
</Row>
|
||||||
|
|
||||||
<!-- 产品设备选择 - 设备相关条件的公共部分 -->
|
<!-- 产品设备选择 - 设备相关条件的公共部分 -->
|
||||||
<el-row v-if="isDeviceCondition" :gutter="16">
|
<Row v-if="isDeviceCondition" :gutter="16">
|
||||||
<el-col :span="12">
|
<Col :span="12">
|
||||||
<el-form-item label="产品" required>
|
<Form.Item label="产品" required>
|
||||||
<ProductSelector
|
<ProductSelector
|
||||||
:model-value="condition.productId"
|
:model-value="condition.productId"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
@@ -186,10 +191,10 @@ const handleOperatorChange = () => {
|
|||||||
"
|
"
|
||||||
@change="handleProductChange"
|
@change="handleProductChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
<el-col :span="12">
|
<Col :span="12">
|
||||||
<el-form-item label="设备" required>
|
<Form.Item label="设备" required>
|
||||||
<DeviceSelector
|
<DeviceSelector
|
||||||
:model-value="condition.deviceId"
|
:model-value="condition.deviceId"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
@@ -198,9 +203,9 @@ const handleOperatorChange = () => {
|
|||||||
:product-id="condition.productId"
|
:product-id="condition.productId"
|
||||||
@change="handleDeviceChange"
|
@change="handleDeviceChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
</el-row>
|
</Row>
|
||||||
|
|
||||||
<!-- 设备状态条件配置 -->
|
<!-- 设备状态条件配置 -->
|
||||||
<div
|
<div
|
||||||
@@ -210,11 +215,11 @@ const handleOperatorChange = () => {
|
|||||||
class="gap-16px flex flex-col"
|
class="gap-16px flex flex-col"
|
||||||
>
|
>
|
||||||
<!-- 状态和操作符选择 -->
|
<!-- 状态和操作符选择 -->
|
||||||
<el-row :gutter="16">
|
<Row :gutter="16">
|
||||||
<!-- 操作符选择 -->
|
<!-- 操作符选择 -->
|
||||||
<el-col :span="12">
|
<Col :span="12">
|
||||||
<el-form-item label="操作符" required>
|
<Form.Item label="操作符" required>
|
||||||
<el-select
|
<Select
|
||||||
:model-value="condition.operator"
|
:model-value="condition.operator"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(value) => updateConditionField('operator', value)
|
(value) => updateConditionField('operator', value)
|
||||||
@@ -222,51 +227,52 @@ const handleOperatorChange = () => {
|
|||||||
placeholder="请选择操作符"
|
placeholder="请选择操作符"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
>
|
>
|
||||||
<el-option
|
<Select.Option
|
||||||
v-for="option in statusOperatorOptions"
|
v-for="option in statusOperatorOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:label="option.label"
|
:label="option.label"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</Select>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
|
|
||||||
<!-- 状态选择 -->
|
<!-- 状态选择 -->
|
||||||
<el-col :span="12">
|
<Col :span="12">
|
||||||
<el-form-item label="设备状态" required>
|
<Form.Item label="设备状态" required>
|
||||||
<el-select
|
<Select
|
||||||
:model-value="condition.param"
|
:model-value="condition.param"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(value) => updateConditionField('param', value)
|
(value: any) => updateConditionField('param', value)
|
||||||
"
|
"
|
||||||
placeholder="请选择设备状态"
|
placeholder="请选择设备状态"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
>
|
>
|
||||||
<el-option
|
<Select.Option
|
||||||
v-for="option in deviceStatusOptions"
|
v-for="option in deviceStatusOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:label="option.label"
|
:label="option.label"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</Select>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
</el-row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 设备属性条件配置 -->
|
<!-- 设备属性条件配置 -->
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
v-else-if="
|
||||||
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
|
condition.type ===
|
||||||
|
IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY.toString()
|
||||||
"
|
"
|
||||||
class="space-y-16px"
|
class="space-y-16px"
|
||||||
>
|
>
|
||||||
<!-- 属性配置 -->
|
<!-- 属性配置 -->
|
||||||
<el-row :gutter="16">
|
<Row :gutter="16">
|
||||||
<!-- 属性/事件/服务选择 -->
|
<!-- 属性/事件/服务选择 -->
|
||||||
<el-col :span="6">
|
<Col :span="6">
|
||||||
<el-form-item label="监控项" required>
|
<Form.Item label="监控项" required>
|
||||||
<PropertySelector
|
<PropertySelector
|
||||||
:model-value="condition.identifier"
|
:model-value="condition.identifier"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
@@ -277,12 +283,12 @@ const handleOperatorChange = () => {
|
|||||||
:device-id="condition.deviceId"
|
:device-id="condition.deviceId"
|
||||||
@change="handlePropertyChange"
|
@change="handlePropertyChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
|
|
||||||
<!-- 操作符选择 -->
|
<!-- 操作符选择 -->
|
||||||
<el-col :span="6">
|
<Col :span="6">
|
||||||
<el-form-item label="操作符" required>
|
<Form.Item label="操作符" required>
|
||||||
<OperatorSelector
|
<OperatorSelector
|
||||||
:model-value="condition.operator"
|
:model-value="condition.operator"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
@@ -291,12 +297,12 @@ const handleOperatorChange = () => {
|
|||||||
:property-type="propertyType"
|
:property-type="propertyType"
|
||||||
@change="handleOperatorChange"
|
@change="handleOperatorChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
|
|
||||||
<!-- 值输入 -->
|
<!-- 值输入 -->
|
||||||
<el-col :span="12">
|
<Col :span="12">
|
||||||
<el-form-item label="比较值" required>
|
<Form.Item label="比较值" required>
|
||||||
<ValueInput
|
<ValueInput
|
||||||
:model-value="condition.param"
|
:model-value="condition.param"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
@@ -306,15 +312,16 @@ const handleOperatorChange = () => {
|
|||||||
:operator="condition.operator"
|
:operator="condition.operator"
|
||||||
:property-config="propertyConfig"
|
:property-config="propertyConfig"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</Form.Item>
|
||||||
</el-col>
|
</Col>
|
||||||
</el-row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 当前时间条件配置 -->
|
<!-- 当前时间条件配置 -->
|
||||||
<CurrentTimeConditionConfig
|
<CurrentTimeConditionConfig
|
||||||
v-else-if="
|
v-else-if="
|
||||||
condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
|
condition.type ===
|
||||||
|
IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME.toString()
|
||||||
"
|
"
|
||||||
:model-value="condition"
|
:model-value="condition"
|
||||||
@update:model-value="updateCondition"
|
@update:model-value="updateCondition"
|
||||||
@@ -323,7 +330,7 @@ const handleOperatorChange = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
:deep(.el-form-item) {
|
:deep(.ant-form-item) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TriggerCondition } from '#/api/iot/rule/scene';
|
import type { TriggerCondition } from '#/api/iot/rule/scene';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import { IotRuleSceneTriggerTimeOperatorEnum } from '#/views/iot/utils/constants';
|
import { IotRuleSceneTriggerTimeOperatorEnum } from '#/views/iot/utils/constants';
|
||||||
|
|||||||
Reference in New Issue
Block a user