feat: 设备树添加管理功能
- 新增设备导入、导出、删除功能及相关API - 封装设备管理逻辑,拆分设备选择与设备管理逻辑 - 添加右键菜单支持设备管理操作
This commit is contained in:
@@ -1 +1,3 @@
|
||||
export * from './use-device-management';
|
||||
export * from './use-device-selection';
|
||||
export * from './use-device-tree';
|
||||
|
||||
184
src/composables/device/use-device-management.ts
Normal file
184
src/composables/device/use-device-management.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { deleteDeviceApi, exportDeviceApi, importDeviceApi, type ImportMsg, type NdmDevicePageQuery, type PageParams, type Station } from '@/apis';
|
||||
import { DEVICE_TYPE_NAMES, type DeviceType } from '@/enums';
|
||||
import { useDeviceStore, useStationStore } from '@/stores';
|
||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { h, onBeforeUnmount } from 'vue';
|
||||
import { useStationDevicesMutation } from '../query';
|
||||
import { isCancel } from 'axios';
|
||||
|
||||
export const useDeviceManagement = () => {
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
|
||||
const { mutate: refreshStationDevices } = useStationDevicesMutation();
|
||||
|
||||
// 导出设备
|
||||
const { mutate: exportDevice } = useMutation({
|
||||
mutationFn: async (params: { deviceType: DeviceType; stationCode: Station['code']; signal?: AbortSignal }) => {
|
||||
const { deviceType, stationCode, signal } = params;
|
||||
const deviceTypeName = DEVICE_TYPE_NAMES[deviceType];
|
||||
const stationDevices = lineDevices.value[stationCode];
|
||||
if (!stationDevices) throw new Error(`该车站没有${deviceTypeName}`);
|
||||
const devices = stationDevices[deviceType];
|
||||
|
||||
const pageQuery: PageParams<NdmDevicePageQuery> = {
|
||||
model: {},
|
||||
extra: {},
|
||||
current: 1,
|
||||
size: devices.length,
|
||||
sort: 'id',
|
||||
order: 'descending',
|
||||
};
|
||||
|
||||
window.$loadingBar.start();
|
||||
|
||||
const data = await exportDeviceApi(deviceType, pageQuery, { stationCode, signal });
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data, { deviceType, stationCode }) => {
|
||||
window.$loadingBar.finish();
|
||||
const time = dayjs().format('YYYY-MM-DD_HH-mm-ss');
|
||||
const stationName = stations.value.find((station) => station.code === stationCode)?.name ?? '';
|
||||
const deviceTypeName = DEVICE_TYPE_NAMES[deviceType];
|
||||
downloadByData(data, `${stationName}_${deviceTypeName}列表_${time}.xlsx`);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
window.$loadingBar.error();
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
// 下载设备导入模板
|
||||
// FIXME: 采用导出空列表的方案,但是后端生成的xlsx中会多一行空行,如果直接再导入该文件就会多导入一个空设备
|
||||
const { mutate: exportDeviceTemplate } = useMutation({
|
||||
mutationFn: async (params: { deviceType: DeviceType; stationCode: Station['code']; signal?: AbortSignal }) => {
|
||||
const { deviceType, stationCode, signal } = params;
|
||||
|
||||
const pageQuery: PageParams<NdmDevicePageQuery> = {
|
||||
model: {},
|
||||
extra: {},
|
||||
current: 1,
|
||||
size: 0,
|
||||
sort: 'id',
|
||||
order: 'descending',
|
||||
};
|
||||
|
||||
window.$loadingBar.start();
|
||||
|
||||
const data = await exportDeviceApi(deviceType, pageQuery, { stationCode, signal });
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data, { deviceType, stationCode }) => {
|
||||
window.$loadingBar.finish();
|
||||
const time = dayjs().format('YYYY-MM-DD_HH-mm-ss');
|
||||
const stationName = stations.value.find((station) => station.code === stationCode)?.name ?? '';
|
||||
const deviceTypeName = DEVICE_TYPE_NAMES[deviceType];
|
||||
downloadByData(data, `${stationName}_${deviceTypeName}导入模板_${time}.xlsx`);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
window.$loadingBar.error();
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
// 导入设备
|
||||
const { mutate: importDevice } = useMutation({
|
||||
mutationFn: async (params: { deviceType: DeviceType; stationCode: Station['code']; signal?: AbortSignal }) => {
|
||||
const { deviceType, stationCode, signal } = params;
|
||||
const data = await new Promise<ImportMsg>((resolve) => {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.xlsx';
|
||||
fileInput.click();
|
||||
fileInput.onchange = async () => {
|
||||
const file = fileInput.files?.[0];
|
||||
// console.log(file);
|
||||
if (!file) {
|
||||
window.$message.error('导入失败');
|
||||
return;
|
||||
}
|
||||
|
||||
window.$loadingBar.start();
|
||||
|
||||
const data = await importDeviceApi(deviceType, file, { stationCode, signal });
|
||||
resolve(data);
|
||||
};
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data, { stationCode, signal }) => {
|
||||
window.$loadingBar.finish();
|
||||
window.$dialog.success({
|
||||
title: '导入成功',
|
||||
content: () => {
|
||||
return h('div', {}, [
|
||||
h('p', {}, `新增数据:${data.insertNum}条`),
|
||||
h('p', {}, `更新数据:${data.updateNum}条`),
|
||||
h('p', {}, `不变数据:${data.unchangedNum}条`),
|
||||
h('p', {}, `错误数据:${data.wrongNum}条`),
|
||||
data.wrongLines.map((line) => h('p', { style: { 'margin-left': '8px' } }, `第${line.rowNum}行:${line.msg}`)),
|
||||
]);
|
||||
},
|
||||
});
|
||||
const station = stations.value.find((station) => station.code === stationCode);
|
||||
if (station) {
|
||||
refreshStationDevices({ station, signal });
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
window.$loadingBar.error();
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
// 删除设备
|
||||
const { mutate: deleteDevice } = useMutation({
|
||||
mutationFn: async (params: { id: string; deviceType: DeviceType; stationCode: Station['code']; signal?: AbortSignal }) => {
|
||||
const { id, deviceType, stationCode, signal } = params;
|
||||
|
||||
window.$loadingBar.start();
|
||||
|
||||
return await deleteDeviceApi(deviceType, id, { stationCode, signal });
|
||||
},
|
||||
onSuccess: (_, { stationCode, signal }) => {
|
||||
window.$loadingBar.finish();
|
||||
window.$message.success('删除成功');
|
||||
const station = stations.value.find((station) => station.code === stationCode);
|
||||
if (station) {
|
||||
refreshStationDevices({ station, signal });
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
window.$loadingBar.error();
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.$loadingBar.finish();
|
||||
});
|
||||
|
||||
return {
|
||||
exportDevice,
|
||||
exportDeviceTemplate,
|
||||
importDevice,
|
||||
deleteDevice,
|
||||
};
|
||||
};
|
||||
112
src/composables/device/use-device-selection.ts
Normal file
112
src/composables/device/use-device-selection.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { LineDevices, NdmDeviceResultVO } from '@/apis';
|
||||
import { tryGetDeviceType, type DeviceType } from '@/enums';
|
||||
import { useDeviceStore } from '@/stores';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export const useDeviceSelection = () => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
|
||||
const selectedStationCode = ref<string>();
|
||||
const selectedDeviceType = ref<DeviceType>();
|
||||
const selectedDevice = ref<NdmDeviceResultVO>();
|
||||
|
||||
const initFromRoute = (lineDevices: LineDevices) => {
|
||||
const { stationCode, deviceType, deviceDbId } = route.query;
|
||||
if (stationCode) {
|
||||
selectedStationCode.value = stationCode as string;
|
||||
}
|
||||
if (deviceType) {
|
||||
selectedDeviceType.value = deviceType as DeviceType;
|
||||
}
|
||||
if (deviceDbId && selectedStationCode.value && selectedDeviceType.value) {
|
||||
const selectedDeviceDbId = deviceDbId as string;
|
||||
const stationDevices = lineDevices[selectedStationCode.value];
|
||||
if (stationDevices) {
|
||||
const devices = stationDevices[selectedDeviceType.value];
|
||||
if (devices) {
|
||||
const device = devices.find((device) => device.id === selectedDeviceDbId);
|
||||
if (device) {
|
||||
selectedDevice.value = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const selectDevice = (device: NdmDeviceResultVO, stationCode: string) => {
|
||||
selectedDevice.value = device;
|
||||
selectedStationCode.value = stationCode;
|
||||
const deviceType = tryGetDeviceType(device.deviceType);
|
||||
if (deviceType) {
|
||||
selectedDeviceType.value = deviceType;
|
||||
}
|
||||
};
|
||||
|
||||
const routeDevice = (device: NdmDeviceResultVO, stationCode: string, to: { path: string }) => {
|
||||
const deviceDbId = device.id;
|
||||
const deviceType = tryGetDeviceType(device.deviceType);
|
||||
router.push({
|
||||
path: to.path,
|
||||
query: {
|
||||
stationCode,
|
||||
deviceType,
|
||||
deviceDbId,
|
||||
from: route.path,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const syncToRoute = () => {
|
||||
const query = { ...route.query };
|
||||
// 当选中的设备发生变化时,删除from参数
|
||||
if (selectedDevice.value?.id && route.query.deviceDbId !== selectedDevice.value.id) {
|
||||
delete query['from'];
|
||||
}
|
||||
if (selectedStationCode.value) {
|
||||
query['stationCode'] = selectedStationCode.value;
|
||||
}
|
||||
if (selectedDeviceType.value) {
|
||||
query['deviceType'] = selectedDeviceType.value;
|
||||
}
|
||||
if (selectedDevice.value?.id) {
|
||||
query['deviceDbId'] = selectedDevice.value.id;
|
||||
}
|
||||
router.replace({ query });
|
||||
};
|
||||
|
||||
watch(selectedDevice, syncToRoute);
|
||||
|
||||
// lineDevices是shallowRef,因此需要深度侦听才能获取内部变化,
|
||||
// 而单纯的深度侦听又可能会引发性能问题,因此尝试使用防抖侦听
|
||||
watchDebounced(
|
||||
lineDevices,
|
||||
(newLineDevices) => {
|
||||
initFromRoute(newLineDevices);
|
||||
},
|
||||
{
|
||||
debounce: 500,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
initFromRoute(lineDevices.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedStationCode,
|
||||
selectedDeviceType,
|
||||
selectedDevice,
|
||||
|
||||
initFromRoute,
|
||||
selectDevice,
|
||||
routeDevice,
|
||||
};
|
||||
};
|
||||
@@ -1,89 +1,12 @@
|
||||
import type { LineDevices, NdmDeviceResultVO } from '@/apis';
|
||||
import { tryGetDeviceType, type DeviceType } from '@/enums';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useDeviceManagement } from './use-device-management';
|
||||
import { useDeviceSelection } from './use-device-selection';
|
||||
|
||||
export const useDeviceTree = () => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const selectedStationCode = ref<string>();
|
||||
const selectedDeviceType = ref<DeviceType>();
|
||||
const selectedDevice = ref<NdmDeviceResultVO>();
|
||||
|
||||
const initFromRoute = (lineDevices: LineDevices) => {
|
||||
const { stationCode, deviceType, deviceDbId } = route.query;
|
||||
if (stationCode) {
|
||||
selectedStationCode.value = stationCode as string;
|
||||
}
|
||||
if (deviceType) {
|
||||
selectedDeviceType.value = deviceType as DeviceType;
|
||||
}
|
||||
if (deviceDbId && selectedStationCode.value && selectedDeviceType.value) {
|
||||
const selectedDeviceDbId = deviceDbId as string;
|
||||
const stationDevices = lineDevices[selectedStationCode.value];
|
||||
if (stationDevices) {
|
||||
const devices = stationDevices[selectedDeviceType.value];
|
||||
if (devices) {
|
||||
const device = devices.find((device) => device.id === selectedDeviceDbId);
|
||||
if (device) {
|
||||
selectedDevice.value = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const selectDevice = (device: NdmDeviceResultVO, stationCode: string) => {
|
||||
selectedDevice.value = device;
|
||||
selectedStationCode.value = stationCode;
|
||||
const deviceType = tryGetDeviceType(device.deviceType);
|
||||
if (deviceType) {
|
||||
selectedDeviceType.value = deviceType;
|
||||
}
|
||||
};
|
||||
|
||||
const routeDevice = (device: NdmDeviceResultVO, stationCode: string, to: { path: string }) => {
|
||||
const deviceDbId = device.id;
|
||||
const deviceType = tryGetDeviceType(device.deviceType);
|
||||
router.push({
|
||||
path: to.path,
|
||||
query: {
|
||||
stationCode,
|
||||
deviceType,
|
||||
deviceDbId,
|
||||
from: route.path,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const syncToRoute = () => {
|
||||
const query = { ...route.query };
|
||||
// 当选中的设备发生变化时,删除from参数
|
||||
if (selectedDevice.value?.id && route.query.deviceDbId !== selectedDevice.value.id) {
|
||||
delete query['from'];
|
||||
}
|
||||
if (selectedStationCode.value) {
|
||||
query['stationCode'] = selectedStationCode.value;
|
||||
}
|
||||
if (selectedDeviceType.value) {
|
||||
query['deviceType'] = selectedDeviceType.value;
|
||||
}
|
||||
if (selectedDevice.value?.id) {
|
||||
query['deviceDbId'] = selectedDevice.value.id;
|
||||
}
|
||||
router.replace({ query });
|
||||
};
|
||||
|
||||
watch(selectedDevice, syncToRoute);
|
||||
const deviceSelection = useDeviceSelection();
|
||||
const deviceManagement = useDeviceManagement();
|
||||
|
||||
return {
|
||||
selectedStationCode,
|
||||
selectedDeviceType,
|
||||
selectedDevice,
|
||||
|
||||
initFromRoute,
|
||||
selectDevice,
|
||||
routeDevice,
|
||||
...deviceSelection,
|
||||
...deviceManagement,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user