优化标点交互
This commit is contained in:
@@ -306,6 +306,12 @@ const configuringDeviceId = ref<string | null>(null);
|
||||
const editingPointId = ref<string | null>(null);
|
||||
const formError = ref("");
|
||||
const mapEditorMode = ref<EditorMode>("select");
|
||||
const draggingPointId = ref<string | null>(null);
|
||||
const pointDropTargetId = ref<string | null>(null);
|
||||
const pointDropPosition = ref<"before" | "after">("before");
|
||||
// 列表点击"前/后插入"后,记录待插入位置,等地图点选坐标后写入该位置
|
||||
const pendingInsertIndex = ref<number | null>(null);
|
||||
const pendingInsertLabel = ref("");
|
||||
const mapStageRef = ref<HTMLElement | null>(null);
|
||||
const mapCanvasRef = ref<HTMLCanvasElement | null>(null);
|
||||
const mapOverviewRef = ref<HTMLCanvasElement | null>(null);
|
||||
@@ -1473,7 +1479,8 @@ async function openConfigDialog(device: DeviceRecord) {
|
||||
deviceConfigs[device.id] = config;
|
||||
configuringDeviceId.value = device.id;
|
||||
configDialogVisible.value = true;
|
||||
mapEditorMode.value = "normal";
|
||||
mapEditorMode.value = "select";
|
||||
cancelInsertPoint();
|
||||
|
||||
if (!config.plans.some((plan) => plan.id === config.activePlanId)) {
|
||||
config.activePlanId = config.plans[0]?.id ?? "";
|
||||
@@ -1515,6 +1522,7 @@ function closeConfigDialog() {
|
||||
configDialogVisible.value = false;
|
||||
planDialogVisible.value = false;
|
||||
configuringDeviceId.value = null;
|
||||
cancelInsertPoint();
|
||||
clearSelectionBox();
|
||||
clearDragPointState();
|
||||
clearMapPanState();
|
||||
@@ -2496,12 +2504,14 @@ function buildAutoPointName(type: PatrolPointType, index: number) {
|
||||
return `${type} ${String(index + 1).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function buildPointName(mode: EditorMode, type: PatrolPointType) {
|
||||
function buildPointName(mode: EditorMode, type: PatrolPointType, index?: number) {
|
||||
const targetIndex = index ?? activeRoundPoints.value.length;
|
||||
|
||||
if (mode === "waypoint") {
|
||||
return buildAutoPointName("途经点", activeRoundPoints.value.length);
|
||||
return buildAutoPointName("途经点", targetIndex);
|
||||
}
|
||||
|
||||
return buildAutoPointName(type, activeRoundPoints.value.length);
|
||||
return buildAutoPointName(type, targetIndex);
|
||||
}
|
||||
|
||||
function buildNextPointId() {
|
||||
@@ -2723,10 +2733,13 @@ function handleMapStageClick(event: MouseEvent) {
|
||||
}
|
||||
|
||||
const type = buildPointTypeFromMode(mapEditorMode.value);
|
||||
const insertIndex = pendingInsertIndex.value;
|
||||
const canInsert =
|
||||
insertIndex !== null && insertIndex >= 0 && insertIndex <= currentConfig.value.points.length;
|
||||
const point: PatrolPoint = {
|
||||
id: buildNextPointId(),
|
||||
planId: currentConfig.value.plans[0]?.id ?? currentConfig.value.activePlanId,
|
||||
name: buildPointName(mapEditorMode.value, type),
|
||||
name: buildPointName(mapEditorMode.value, type, insertIndex ?? undefined),
|
||||
type,
|
||||
direction: type === "业务点" ? "正向" : "正向",
|
||||
x: position.x,
|
||||
@@ -2743,7 +2756,14 @@ function handleMapStageClick(event: MouseEvent) {
|
||||
note: "新建点位,待补充巡检说明",
|
||||
};
|
||||
|
||||
if (canInsert && insertIndex !== null) {
|
||||
currentConfig.value.points.splice(insertIndex, 0, point);
|
||||
cancelInsertPoint();
|
||||
mapEditorMode.value = "select";
|
||||
} else {
|
||||
currentConfig.value.points.push(point);
|
||||
}
|
||||
|
||||
updateSelection([point.id]);
|
||||
}
|
||||
|
||||
@@ -2930,6 +2950,86 @@ function removePoint(pointId: string) {
|
||||
updateSelection(nextPoint ? [nextPoint.id] : []);
|
||||
}
|
||||
|
||||
// 共享巡检点顺序即巡检顺序,可通过列表拖拽调整
|
||||
function handlePointDragStart(point: PatrolPoint, event: DragEvent) {
|
||||
cancelInsertPoint();
|
||||
draggingPointId.value = point.id;
|
||||
pointDropTargetId.value = null;
|
||||
pointDropPosition.value = "before";
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", point.id);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePointDragOver(point: PatrolPoint, event: DragEvent) {
|
||||
if (!draggingPointId.value || draggingPointId.value === point.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
}
|
||||
|
||||
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
|
||||
pointDropTargetId.value = point.id;
|
||||
pointDropPosition.value = event.clientY > rect.top + rect.height / 2 ? "after" : "before";
|
||||
}
|
||||
|
||||
function handlePointDrop(point: PatrolPoint) {
|
||||
const sourceId = draggingPointId.value;
|
||||
const config = currentConfig.value;
|
||||
|
||||
if (!sourceId || !config || sourceId === point.id) {
|
||||
resetPointDragState();
|
||||
return;
|
||||
}
|
||||
|
||||
const fromIndex = config.points.findIndex((item) => item.id === sourceId);
|
||||
|
||||
if (fromIndex < 0 || !config.points.some((item) => item.id === point.id)) {
|
||||
resetPointDragState();
|
||||
return;
|
||||
}
|
||||
|
||||
const [movedPoint] = config.points.splice(fromIndex, 1);
|
||||
const targetIndex = config.points.findIndex((item) => item.id === point.id);
|
||||
const insertIndex = pointDropPosition.value === "after" ? targetIndex + 1 : targetIndex;
|
||||
|
||||
config.points.splice(insertIndex, 0, movedPoint);
|
||||
resetPointDragState();
|
||||
|
||||
nextTick(() => {
|
||||
scheduleMapCanvasRender();
|
||||
});
|
||||
}
|
||||
|
||||
function resetPointDragState() {
|
||||
draggingPointId.value = null;
|
||||
pointDropTargetId.value = null;
|
||||
pointDropPosition.value = "before";
|
||||
}
|
||||
|
||||
// 在指定点位前/后插入新点位:记录插入位置,随后在地图上点选坐标
|
||||
function startInsertPoint(anchorIndex: number, position: "before" | "after") {
|
||||
if (!currentConfig.value?.mapLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
pendingInsertIndex.value = position === "before" ? anchorIndex : anchorIndex + 1;
|
||||
pendingInsertLabel.value = `点位 ${anchorIndex + 1} ${position === "before" ? "之前" : "之后"}`;
|
||||
mapEditorMode.value = "normal";
|
||||
}
|
||||
|
||||
function cancelInsertPoint() {
|
||||
pendingInsertIndex.value = null;
|
||||
pendingInsertLabel.value = "";
|
||||
}
|
||||
|
||||
function loadPlan(point: PatrolPoint) {
|
||||
if (!activeRound.value) {
|
||||
return;
|
||||
@@ -3359,6 +3459,13 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="editor-toolbar">
|
||||
<button class="editor-tool-btn icon" :class="{ active: mapEditorMode === 'select' }" type="button"
|
||||
title="拖动查看" aria-label="拖动查看" @click="cancelInsertPoint(); mapEditorMode = 'select'">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-for="mode in editorModes" :key="mode.id" :class="[
|
||||
'editor-tool-btn',
|
||||
{ active: mapEditorMode === mode.id },
|
||||
@@ -3368,6 +3475,10 @@ watch(
|
||||
<button class="editor-tool-btn" type="button" @click="clearCanvas">
|
||||
清空巡检点
|
||||
</button>
|
||||
<div v-if="pendingInsertIndex !== null" class="editor-tip">
|
||||
在地图上点击放置新点位,将插入到{{ pendingInsertLabel }}
|
||||
<button class="editor-tip-cancel" type="button" @click="cancelInsertPoint">取消插入</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-map-stage" ref="mapStageRef">
|
||||
@@ -3442,14 +3553,28 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="rounds-point-column">
|
||||
<div class="sub-panel-header">
|
||||
<div class="sub-panel-title">共享巡检点</div>
|
||||
<span class="sub-panel-hint">拖拽调整巡检顺序</span>
|
||||
</div>
|
||||
<div class="point-list-shell">
|
||||
<div v-for="(point, index) in activeRoundPoints" :key="point.id" :class="[
|
||||
'point-list-item',
|
||||
{
|
||||
active: currentConfig.selectedPointIds.includes(point.id),
|
||||
dragging: draggingPointId === point.id,
|
||||
'drop-before': pointDropTargetId === point.id && pointDropPosition === 'before',
|
||||
'drop-after': pointDropTargetId === point.id && pointDropPosition === 'after',
|
||||
},
|
||||
]" @click="editPoint(point.id)">
|
||||
]" draggable="true" @click="editPoint(point.id)" @dragstart="handlePointDragStart(point, $event)"
|
||||
@dragover="handlePointDragOver(point, $event)" @drop.prevent="handlePointDrop(point)"
|
||||
@dragend="resetPointDragState">
|
||||
<span class="point-drag-handle" title="拖拽调整顺序" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M8 6h2v2H8V6zm6 0h2v2h-2V6zM8 11h2v2H8v-2zm6 0h2v2h-2v-2zM8 16h2v2H8v-2zm6 0h2v2h-2v-2z" />
|
||||
</svg>
|
||||
</span>
|
||||
<div class="point-list-main">
|
||||
<div class="point-list-title">点位 {{ index + 1 }}</div>
|
||||
<div class="point-list-meta">
|
||||
@@ -3460,6 +3585,18 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
<div class="point-list-actions">
|
||||
<button class="icon" type="button" :title="`在点位 ${index + 1} 之前插入新点位`"
|
||||
aria-label="在此点位前插入" @click.stop="startInsertPoint(index, 'before')">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M11 18h2V8.83l4.59 4.58L19 12l-7-7-7 7 1.41 1.41L11 8.83V18z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon" type="button" :title="`在点位 ${index + 1} 之后插入新点位`"
|
||||
aria-label="在此点位后插入" @click.stop="startInsertPoint(index, 'after')">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M13 6h-2v9.17l-4.59-4.58L5 12l7 7 7-7-1.41-1.41L13 15.17V6z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" @click.stop="openPlanDialog(point)">编辑</button>
|
||||
<button class="danger" type="button" @click.stop="removePoint(point.id)">
|
||||
删除
|
||||
@@ -4015,6 +4152,20 @@ watch(
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-tool-btn.icon {
|
||||
width: 34px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.editor-tool-btn.icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.editor-tool-btn.active {
|
||||
color: #eefcff;
|
||||
border-color: rgba(102, 235, 255, 0.7);
|
||||
@@ -4025,10 +4176,23 @@ watch(
|
||||
|
||||
.editor-tip {
|
||||
margin-left: auto;
|
||||
color: rgba(161, 218, 243, 0.72);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: rgba(255, 214, 130, 0.92);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.editor-tip-cancel {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #8fd6ff;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.map-meta-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -4753,9 +4917,58 @@ watch(
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 196, 118, 0.12);
|
||||
}
|
||||
|
||||
.sub-panel-hint {
|
||||
color: rgba(161, 218, 243, 0.72);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.point-drag-handle {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(140, 205, 235, 0.6);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.point-drag-handle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.point-list-item.dragging {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.point-list-item.drop-before {
|
||||
box-shadow: inset 0 2px 0 0 rgba(102, 235, 255, 0.85);
|
||||
}
|
||||
|
||||
.point-list-item.drop-after {
|
||||
box-shadow: inset 0 -2px 0 0 rgba(102, 235, 255, 0.85);
|
||||
}
|
||||
|
||||
.point-list-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.point-list-actions button.icon {
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.point-list-actions button.icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.point-list-actions .danger,
|
||||
|
||||
@@ -31,12 +31,12 @@ export default defineConfig(({ command }: ConfigEnv) => {
|
||||
host: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: `http://192.168.3.233:16003/`,
|
||||
target: `http://192.168.3.110:16003/`,
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/api/, '')
|
||||
},
|
||||
'/ws': {
|
||||
target: `ws://192.168.3.233:16003/`,
|
||||
target: `ws://192.168.3.110:16003/`,
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user