feat: 更新库位面板逻辑,新增库位搜索功能并优化点位和库位的显示逻辑

This commit is contained in:
xudan 2025-09-09 11:16:52 +08:00
parent 0a664afd14
commit 1be4280495
4 changed files with 132 additions and 44 deletions

View File

@ -1,29 +1,26 @@
<script setup lang="ts">
import { MapAreaType, type MapPen, MapPointType, MapRoutePassType } from '@api/map';
import type { EditorService } from '@core/editor.service';
import { computed, inject, type InjectionKey, ref, type ShallowRef, watch } from 'vue';
import { MapAreaType, type MapPen, MapPointType, MapRoutePassType } from '../apis/map';
import type { EditorService } from '../services/editor.service';
type Props = {
token: InjectionKey<ShallowRef<EditorService>>;
current?: string;
onlyArea1?: boolean;
onlyStorage?: boolean;
};
const props = defineProps<Props>();
const editor = inject(props.token)!;
const keyword = ref<string>('');
const activeKeys = ref<string[]>(['defaultActive']);
const activeKeys = ref<string[]>(['库位']);
//#region
const points = computed<MapPen[]>(() =>
editor.value.points.value.filter(({ label, point }) => {
editor.value.points.value.filter(({ label }) => {
if (!keyword.value) return true;
const labelMatch = label?.includes(keyword.value);
// associatedStorageLocations
const storageLocationMatch =
point?.type === MapPointType.动作点 &&
point.associatedStorageLocations?.some((location) => location.includes(keyword.value));
return labelMatch || storageLocationMatch;
return label?.includes(keyword.value);
}),
);
//#endregion
@ -38,12 +35,65 @@ const routes = computed<MapPen[]>(() =>
const areas = computed<MapPen[]>(() => editor.value.areas.value.filter(({ label }) => label?.includes(keyword.value)));
//#endregion
//#region
interface StorageLocationItem {
id: string;
name: string;
pointId: string;
pointLabel: string;
isOccupied: boolean;
isLocked: boolean;
isDisabled: boolean;
isEmptyTray: boolean;
status: 'available' | 'occupied' | 'locked' | 'disabled' | 'unknown';
}
const storageLocations = computed<StorageLocationItem[]>(() => {
const allStorageLocations: StorageLocationItem[] = [];
//
editor.value.points.value
.filter(({ point }) => point?.type === MapPointType.动作点 && point.associatedStorageLocations?.length)
.forEach(({ id, label, point }) => {
if (point?.associatedStorageLocations) {
point.associatedStorageLocations.forEach((locationName) => {
allStorageLocations.push({
id: `${id}-${locationName}`,
name: locationName,
pointId: id!,
pointLabel: label || id!,
isOccupied: false, //
isLocked: false,
isDisabled: false,
isEmptyTray: false,
status: 'unknown'
});
});
}
});
//
if (!keyword.value) return allStorageLocations;
return allStorageLocations.filter(({ name }) =>
name.includes(keyword.value)
);
});
//#endregion
//#region
const updateActiveKeys = () => {
if (!keyword.value) return;
const keys: string[] = [];
// only-storage
if (!keyword.value) {
if (props.onlyStorage) {
keys.push('库位');
}
activeKeys.value = keys;
return;
}
//
const pointTypes = [
{ key: '普通点', type: MapPointType.普通点 },
@ -79,6 +129,7 @@ const updateActiveKeys = () => {
//
const areaTypes = [
{ key: '库区', type: MapAreaType.库区 },
{ key: '互斥区', type: MapAreaType.互斥区 },
{ key: '非互斥区', type: MapAreaType.非互斥区 },
];
@ -88,6 +139,15 @@ const updateActiveKeys = () => {
if (hasMatch) keys.push(key);
});
// - only-storage
if (props.onlyStorage) {
// only-storage
keys.push('库位');
} else if (keyword.value && storageLocations.value.length > 0) {
//
keys.push('库位');
}
activeKeys.value = keys;
};
//#endregion
@ -97,6 +157,13 @@ const select = (id: string) => {
editor.value.gotoById(id);
};
//
const selectStorageLocation = (storageLocation: StorageLocationItem) => {
editor.value.active(storageLocation.pointId);
editor.value.gotoById(storageLocation.pointId);
};
watch(keyword, updateActiveKeys);
</script>
@ -113,16 +180,16 @@ watch(keyword, updateActiveKeys);
<i class="icon dropdown" :class="{ active: v?.isActive }" />
</template>
<a-collapse-panel v-if="onlyArea1" :header="$t('库区')" key="defaultActive">
<a-list rowKey="id" :data-source="areas.filter(({ area }) => area?.type === MapAreaType.库区)">
<a-collapse-panel v-if="onlyStorage" :header="$t('库位')" key="库位">
<a-list rowKey="id" :data-source="storageLocations">
<template #renderItem="{ item }">
<a-list-item
class="ph-16"
:class="{ selected: item.id === current }"
:class="{ selected: item.pointId === current }"
style="height: 36px"
@click="select(item.id)"
@click="selectStorageLocation(item)"
>
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
<a-typography-text type="secondary">{{ item.name }}</a-typography-text>
</a-list-item>
</template>
</a-list>
@ -280,23 +347,27 @@ watch(keyword, updateActiveKeys);
style="height: 36px"
@click="select(item.id)"
>
<a-typography-text type="secondary">
{{ item.label }}
<a-tag
v-if="item.point?.associatedStorageLocations?.length"
size="small"
type="primary"
style="font-size: 12px; margin-left: 8px; vertical-align: middle; height: 22px"
>
{{
keyword
? item.point.associatedStorageLocations
.filter((location: string) => location.includes(keyword))
.join(', ')
: item.point.associatedStorageLocations.join(', ')
}}
</a-tag>
</a-typography-text>
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
</a-list-item>
</template>
</a-list>
</a-collapse-panel>
<!-- 库位面板 - only-storage 模式下始终显示在普通模式下搜索时显示 -->
<a-collapse-panel
v-if="(props.onlyStorage || (keyword && storageLocations.length > 0))"
:header="$t('库位')"
key="库位"
>
<a-list rowKey="id" :data-source="storageLocations">
<template #renderItem="{ item }">
<a-list-item
class="ph-16"
:class="{ selected: item.pointId === current }"
style="height: 36px"
@click="selectStorageLocation(item)"
>
<a-typography-text type="secondary">{{ item.name }}</a-typography-text>
</a-list-item>
</template>
</a-list>
@ -385,6 +456,21 @@ watch(keyword, updateActiveKeys);
</a-collapse-panel>
<!-- 区域类型面板 -->
<a-collapse-panel :header="$t('库区')" key="库区">
<a-list rowKey="id" :data-source="areas.filter(({ area }) => area?.type === MapAreaType.库区)">
<template #renderItem="{ item }">
<a-list-item
class="ph-16"
:class="{ selected: item.id === current }"
style="height: 36px"
@click="select(item.id)"
>
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
</a-list-item>
</template>
</a-list>
</a-collapse-panel>
<a-collapse-panel :header="$t('互斥区')" key="互斥区">
<a-list rowKey="id" :data-source="areas.filter(({ area }) => area?.type === MapAreaType.互斥区)">
<template #renderItem="{ item }">
@ -414,6 +500,7 @@ watch(keyword, updateActiveKeys);
</template>
</a-list>
</a-collapse-panel>
</template>
</a-collapse>
</a-flex>

View File

@ -1,21 +1,20 @@
<script setup lang="ts">
import type { RobotRealtimeInfo } from '@api/robot';
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '@api/scene';
import { EditorService } from '@core/editor.service';
import { StorageLocationService } from '@core/storage-location.service';
import { useViewState } from '@core/useViewState';
import { message } from 'ant-design-vue';
import { isNil } from 'lodash-es';
import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
import { useRoute } from 'vue-router';
import type { RobotRealtimeInfo } from '../apis/robot';
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '../apis/scene';
import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';
import colorConfig from '../services/color/color-config.service';
import {
type ContextMenuState,
createContextMenuManager,
handleContextMenuFromPenData,
isClickInsideMenu} from '../services/context-menu.service';
import { EditorService } from '../services/editor.service';
import { StorageLocationService } from '../services/storage-location.service';
import { useViewState } from '../services/useViewState';
const EDITOR_KEY = Symbol('editor-key');
@ -442,8 +441,8 @@ const handleGlobalKeydown = (event: KeyboardEvent) => {
<a-tab-pane key="1" :tab="$t('机器人')">
<RobotGroups v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
</a-tab-pane>
<a-tab-pane key="2" :tab="$t('库')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
<a-tab-pane key="2" :tab="$t('库')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-storage />
</a-tab-pane>
<a-tab-pane key="3" :tab="$t('高级组')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />

View File

@ -234,8 +234,8 @@ const handleAutoCreateStorageCancel = () => {
show-group-edit
/>
</a-tab-pane>
<a-tab-pane key="2" :tab="$t('库')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
<a-tab-pane key="2" :tab="$t('库')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-storage />
</a-tab-pane>
<a-tab-pane key="3" :tab="$t('高级组')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />

View File

@ -9,6 +9,8 @@ export interface ContextMenuState {
y: number;
menuType: 'default' | 'storage' | 'storage-background' | 'robot' | 'point' | 'area';
eventData?: any;
storageLocations?: any[];
robotInfo?: any;
}
/**