董国庆
2026-05-26 d56b71d468460d2bd39bd5a7a1cb557f6a889f90
feat(operationWorkbench): 优化禁行区编辑并临时屏蔽运营策略配置

- 为电子名称表格列添加超出省略显示,优化表格展示效果
- 新增禁行区顶点计数器,展示当前顶点数与最大限制数
- 限制禁行区最多支持16个顶点,超出时自动截断并提示用户
- 调整运营区域与禁行区的配色为橙色系,优化视觉表现
- 完善禁行区编辑的交互逻辑与校验处理流程
- 针对测试运营工作台场景,临时注释禁行区运营策略配置的相关代码
4个文件已修改
269 ■■■■■ 已修改文件
src/views/testDemo/operationWorkbench/components/RestrictedAreaModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/list.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/map.scss 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/map.vue 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/components/RestrictedAreaModal.vue
@@ -98,6 +98,7 @@
            clearable
          />
        </n-form-item>
        <!-- 测试运营工作台暂时不需要禁行区运营策略配置,先注释整块 UI,后续确认后再统一删除。
        <div class="box flex flex-col gap-8px">
          <div class="box-title">运营策略</div>
          <div
@@ -356,6 +357,7 @@
            <span>添加运营策略组</span>
          </div>
        </div>
        -->
      </div>
    </n-form>
    <template #action>
src/views/testDemo/operationWorkbench/list.vue
@@ -1755,6 +1755,7 @@
        key: 'electronicName',
        width: 150,
        resizable: true,
        ellipsis: true,
        render(row) {
          return (
            <NTooltip trigger="hover">
src/views/testDemo/operationWorkbench/map.scss
@@ -1363,6 +1363,32 @@
  }
}
.restricted-vertex-counter {
  display: inline-flex;
  align-items: center;
  gap: 24px;
  height: 32px;
  margin-left: 8px;
  padding: 0 12px;
  border: 1px solid rgba(23, 31, 38, 0.15);
  border-radius: 4px;
  background: #fff;
  box-shadow: 0 2px 10px rgba(23, 31, 38, 0.06);
}
.restricted-vertex-counter__label {
  color: rgba(23, 31, 38, 0.7);
  font-size: 13px;
  line-height: 18px;
}
.restricted-vertex-counter__value {
  color: #171f26;
  font-size: 16px;
  font-weight: 600;
  line-height: 20px;
}
.coverage-detail {
  position: absolute;
  top: 32px;
src/views/testDemo/operationWorkbench/map.vue
@@ -444,6 +444,7 @@
const fenceDraftPolygon = shallowRef<any>(null);
const fenceDraftRestrictedPolygons = shallowRef<any[]>([]);
const fenceDraftPoints = ref<LngLatTuple[]>([]);
const restrictedDrawingVertexCount = ref(0);
const fenceRestrictedAreas = ref<FenceRestrictedAreaDraft[]>([]);
const activeRestrictedAreaId = ref("");
const readonlyRestrictedAreaId = ref("");
@@ -452,6 +453,7 @@
const restrictedDrawSessionId = ref(0);
const restrictedAreaEditorClosed = ref(false);
const suppressRestrictedEditorEnd = ref(false);
let restrictedEditorRollbacking = false;
const restrictedAreaModalVisible = ref(false);
const fencePanelRef = ref<any>(null);
const restrictedAreaModalRef = ref<any>(null);
@@ -795,6 +797,11 @@
const isFenceEditWorkflow = computed(
  () => fenceEditMode.value && !fenceCreateMode.value
);
const isFenceGeometryLocked = computed(
  () =>
    isFenceEditWorkflow.value &&
    Number(fenceForm.value.areaStatus || fenceDetailData.value?.areaStatus || 1) === 1
);
const isStationFormMode = computed(
  () =>
    activeTab.value === "station" && (stationEditMode.value || stationCreateMode.value)
@@ -838,6 +845,23 @@
  if (!Number.isFinite(lon) || !Number.isFinite(lat)) return null;
  return [lon, lat];
});
const restrictedAreaVertexLimit = 16;
const currentRestrictedVertexCount = computed(() => {
  if (fenceInteractionMode.value === "restricted-draw" && fenceMouseTool.value)
    return restrictedDrawingVertexCount.value;
  const activeRestrictedArea = fenceRestrictedAreas.value.find(
    (item) => item.id === activeRestrictedAreaId.value
  );
  return activeRestrictedArea?.points.length || 0;
});
const showRestrictedVertexCounter = computed(() => {
  if (!isFenceFormMode.value || activeTab.value !== "fence") return false;
  if (isFenceGeometryLocked.value) return false;
  if (!activeRestrictedAreaId.value) return false;
  return ["restricted-draw", "restricted-edit"].includes(
    fenceInteractionMode.value
  );
});
const fenceMapTipText = computed(() => {
  if (!isFenceFormMode.value) return "";
  const activeRestrictedArea = fenceRestrictedAreas.value.find(
@@ -846,6 +870,7 @@
  if (activeRestrictedArea) {
    if (activeRestrictedArea.points.length < 3)
      return "请在地图上【蓝色运营区域】绘制多边形区域,双击闭合";
    if (isFenceGeometryLocked.value) return "【红色禁行区】已选中";
    return "【红色禁行区】可调整区域点位,请保持在蓝色运营区内";
  }
  if (fenceDraftPoints.value.length < 3)
@@ -1783,8 +1808,40 @@
  activeRestrictedAreaId.value = "";
  readonlyRestrictedAreaId.value = "";
  fenceDraftPoints.value = [];
  restrictedDrawingVertexCount.value = 0;
  invalidFenceStationIds.value = [];
  updateFenceStationWarningStyles();
}
function getRestrictedDrawingVertexCount() {
  const path = fenceMouseTool.value?.polygonTemp?.obj?.getPath?.();
  if (!Array.isArray(path) || !path.length) return 0;
  return path.length > 1 ? path.length - 1 : path.length;
}
function refreshRestrictedDrawingVertexCount() {
  restrictedDrawingVertexCount.value = getRestrictedDrawingVertexCount();
}
function applyRestrictedPolygonPath(polygon: any, points: LngLatTuple[]) {
  polygon?.setPath?.(
    points.map(([lng, lat]) => new (window as any).AMap.LngLat(lng, lat))
  );
}
function enforceRestrictedDrawingVertexLimit() {
  const tempPolygon = fenceMouseTool.value?.polygonTemp?.obj;
  const path = tempPolygon?.getPath?.();
  if (!Array.isArray(path) || !path.length) return false;
  const vertexCount = getRestrictedDrawingVertexCount();
  if (vertexCount <= restrictedAreaVertexLimit) return false;
  const retainedLength = Math.min(path.length, restrictedAreaVertexLimit + 1);
  tempPolygon.setPath?.(path.slice(0, retainedLength));
  restrictedDrawingVertexCount.value = restrictedAreaVertexLimit;
  window.$message?.warning(
    `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位`
  );
  return true;
}
function updateFenceStationWarningStyles() {
@@ -1808,12 +1865,12 @@
function setFenceDraftPolygonStyle(editable: boolean) {
  if (!fenceDraftPolygon.value) return;
  fenceDraftPolygon.value.setOptions({
    fillColor: "#0068FF",
    strokeColor: "#0068FF",
    fillColor: "#F19216",
    strokeColor: "#F19216",
    strokeWeight: 2,
    strokeOpacity: 1,
    fillOpacity: 0.1,
    strokeStyle: editable ? "solid" : "dashed",
    strokeStyle: "solid",
    cursor: "pointer",
    zIndex: 220,
  });
@@ -1864,12 +1921,12 @@
  if (fenceDraftPolygon.value) map.value.remove(fenceDraftPolygon.value);
  fenceDraftPolygon.value = new (window as any).AMap.Polygon({
    path: points,
    fillColor: "#0068FF",
    strokeColor: "#0068FF",
    fillColor: "#F19216",
    strokeColor: "#F19216",
    strokeWeight: 2,
    strokeOpacity: 1,
    fillOpacity: 0.1,
    strokeStyle: "dashed",
    strokeStyle: "solid",
    bubble: true,
    zIndex: 220,
  });
@@ -1894,12 +1951,12 @@
    fenceDraftPolygon.value,
    {
      controlPoint: {
        strokeColor: "#0068FF",
        strokeColor: "#F19216",
        strokeOpacity: 1,
        strokeWeight: 2,
      },
      midControlPoint: { fillColor: "#0068FF" },
      editOptions: { fillColor: "#0068FF" },
      midControlPoint: { fillColor: "#F19216" },
      editOptions: { fillColor: "#F19216" },
    }
  );
  fencePolyEditor.value.open();
@@ -1926,10 +1983,10 @@
  cleanupFenceEditorState();
  fenceMouseTool.value = new (window as any).AMap.MouseTool(map.value);
  fenceMouseTool.value.polygon({
    strokeColor: "#0068FF",
    strokeColor: "#F19216",
    strokeOpacity: 1,
    strokeWeight: 2,
    fillColor: "#0068FF",
    fillColor: "#F19216",
    fillOpacity: 0.1,
    strokeStyle: "solid",
    cursor: "pointer",
@@ -1967,7 +2024,7 @@
  cleanupFenceEditorState();
  fenceDraftPoints.value = points;
  createFenceDraftPolygon(points);
  enableFenceDraftEdit();
  if (!isFenceGeometryLocked.value) enableFenceDraftEdit();
  fenceRestrictedAreas.value = (
    Array.isArray(detail?.electronicFenceAreas)
      ? detail.electronicFenceAreas
@@ -2192,29 +2249,30 @@
}
function updateRestrictedPolygonStyles() {
  const isOperationAreaActive =
    isFenceFormMode.value &&
    !activeRestrictedAreaId.value &&
    fenceDraftPoints.value.length >= 3;
  fenceRestrictedAreas.value.forEach((item) => {
    const polygon = item.polygon;
    if (!polygon?.setOptions) return;
    const isActive = item.id === activeRestrictedAreaId.value;
    const isEditing =
      !isFenceGeometryLocked.value &&
      item.id === activeRestrictedEditorId.value;
    item.isActive = isActive;
    try {
      polygon.setOptions({
        strokeColor: isActive
        strokeColor: isEditing
          ? "#F14637"
          : isOperationAreaActive
          ? "transparent"
          : "#EF938B",
          : "transparent",
        fillColor: isActive ? "#F14637" : "#EF938B",
        strokeWeight: 0,
        strokeOpacity: 0,
        fillOpacity: isActive ? 0.32 : isOperationAreaActive ? 1 : 0.18,
        strokeWeight: isEditing ? 1 : 0,
        strokeOpacity: isEditing ? 1 : 0,
        fillOpacity: isEditing
          ? 0
          : isActive
          ? 1
          : 0.18,
        strokeStyle: "solid",
        cursor: "pointer",
        zIndex: isActive ? 230 : 210,
        zIndex: isEditing ? 230 : isActive ? 220 : 210,
      });
    } catch {
      item.polygon = null;
@@ -2254,6 +2312,20 @@
  updateRestrictedPolygonStyles();
}
function syncLockedFenceReadonlySelection(restrictedAreaId = "") {
  if (!map.value) return;
  fenceInteractionMode.value = "operation-preview";
  activeRestrictedAreaId.value = restrictedAreaId;
  activeRestrictedEditorId.value = "";
  restrictedAreaEditorClosed.value = false;
  suppressRestrictedEditorEnd.value = true;
  hideFenceEditGuide();
  fencePolyEditor.value?.close?.();
  fencePolyEditor.value = null;
  setFenceDraftPolygonStyle(false);
  renderFenceRestrictedPolygons();
}
function focusOperationArea(fitView = false) {
  if (
    !isFenceFormMode.value ||
@@ -2261,6 +2333,16 @@
    fenceDraftPoints.value.length < 3
  )
    return;
  if (isFenceGeometryLocked.value) {
    syncLockedFenceReadonlySelection();
    if (fitView && map.value)
      map.value.setFitView(
        [fenceDraftPolygon.value],
        false,
        [120, 120, 120, 120]
      );
    return;
  }
  applyFenceInteractionMode("operation-edit");
  enableFenceDraftEdit();
  if (fitView && map.value)
@@ -2290,8 +2372,24 @@
    return;
  }
  const area = fenceRestrictedAreas.value.find((item) => item.id === id);
  const isCurrentRestrictedAreaActive = activeRestrictedAreaId.value === id;
  if (isFenceGeometryLocked.value) {
    if (isCurrentRestrictedAreaActive) {
      syncLockedFenceReadonlySelection();
      return;
    }
    syncLockedFenceReadonlySelection(id);
    const selectedArea = fenceRestrictedAreas.value.find((item) => item.id === id);
    if (selectedArea?.polygon && map.value)
      map.value.setFitView([selectedArea.polygon], false, [120, 120, 120, 120]);
    return;
  }
  if (!area?.polygon) {
    startRestrictedAreaDraw(id);
    return;
  }
  if (isCurrentRestrictedAreaActive && !fenceMouseTool.value) {
    focusOperationArea();
    return;
  }
  if (
@@ -2306,6 +2404,7 @@
  if (area?.polygon && map.value) {
    map.value.setFitView([area.polygon], false, [120, 120, 120, 120]);
    if (!isFenceFormMode.value) return;
    let lastValidPoints = clonePolygonPath(area.points);
    fencePolyEditor.value?.close?.();
    fencePolyEditor.value = new (window as any).AMap.PolyEditor(
      map.value,
@@ -2324,12 +2423,34 @@
      const current = fenceRestrictedAreas.value.find((item) => item.id === id);
      const polygon = current?.polygon;
      if (!current || !polygon?.getPath) return;
      current.points = polygon
      const nextPoints = polygon
        .getPath()
        .map((point: any) => [point.lng, point.lat] as LngLatTuple);
      if (
        nextPoints.length > restrictedAreaVertexLimit &&
        lastValidPoints.length >= 3 &&
        !restrictedEditorRollbacking
      ) {
        restrictedEditorRollbacking = true;
        window.$message?.warning(
          `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位`
        );
        applyRestrictedPolygonPath(polygon, lastValidPoints);
        restrictedDrawingVertexCount.value = lastValidPoints.length;
        setTimeout(() => {
          restrictedEditorRollbacking = false;
        }, 0);
        return;
      }
      current.points = nextPoints;
      current.area = calcFenceArea(current.points);
      restrictedDrawingVertexCount.value = current.points.length;
      if (current.points.length >= 3) {
        lastValidPoints = clonePolygonPath(current.points);
      }
    };
    const handleRestrictedAreaEditEnd = () => {
      if (restrictedEditorRollbacking) return;
      if (suppressRestrictedEditorEnd.value) {
        suppressRestrictedEditorEnd.value = false;
        return;
@@ -2369,6 +2490,8 @@
    };
    fencePolyEditor.value.on("end", handleRestrictedAreaEditEnd);
    fencePolyEditor.value.on("adjust", syncPoints);
    fencePolyEditor.value.on("addnode", syncPoints);
    fencePolyEditor.value.on("removenode", syncPoints);
    fencePolyEditor.value.open();
  }
}
@@ -2378,8 +2501,10 @@
    window.$message?.warning("请绘制至少3个点的禁行区");
    return false;
  }
  if (points.length > 16) {
    window.$message?.warning("禁行区最多支持设置 16 个点位");
  if (points.length > restrictedAreaVertexLimit) {
    window.$message?.warning(
      `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位`
    );
    return false;
  }
  if (isFencePolygonSelfIntersect(points)) {
@@ -2409,6 +2534,7 @@
  restrictedDrawSessionId.value += 1;
  const currentSessionId = restrictedDrawSessionId.value;
  applyFenceInteractionMode("restricted-draw", areaId);
  restrictedDrawingVertexCount.value = 0;
  const previousMouseTool = fenceMouseTool.value;
  fenceMouseTool.value = null;
  previousMouseTool?.close?.();
@@ -2434,14 +2560,17 @@
    const points = event.obj
      .getPath()
      .map((point: any) => [point.lng, point.lat] as LngLatTuple);
    restrictedDrawingVertexCount.value = points.length;
    map.value.remove(event.obj);
    if (points.length < 3) {
      window.$message?.warning("请绘制至少3个点的禁行区");
      setTimeout(() => startRestrictedAreaDraw(areaId), 0);
      return;
    }
    if (points.length > 16) {
      window.$message?.warning("禁行区最多支持设置 16 个点位");
    if (points.length > restrictedAreaVertexLimit) {
      window.$message?.warning(
        `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位`
      );
      setTimeout(() => startRestrictedAreaDraw(areaId), 0);
      return;
    }
@@ -2536,17 +2665,19 @@
  } catch {
    return;
  }
  if (!restrictedAreaForm.value.operationStrategy.length) {
    window.$message?.warning("请至少添加一个禁行区策略组");
    return;
  }
  if (!validateRestrictedAreaStrategies()) return;
  // 测试运营工作台暂时不需要禁行区运营策略,先注释相关校验逻辑,后续确认后再统一删除。
  // if (!restrictedAreaForm.value.operationStrategy.length) {
  //   window.$message?.warning("请至少添加一个禁行区策略组");
  //   return;
  // }
  // if (!validateRestrictedAreaStrategies()) return;
  current.areaName = restrictedAreaForm.value.areaName.trim();
  current.areaDescription = restrictedAreaForm.value.areaDescription.trim();
  current.areaStatus = restrictedAreaForm.value.areaStatus;
  current.operationStrategy = cloneRestrictedAreaStrategies(
    restrictedAreaForm.value.operationStrategy
  );
  // 测试运营工作台暂时不需要禁行区运营策略,先注释相关回写逻辑,后续确认后再统一删除。
  // current.operationStrategy = cloneRestrictedAreaStrategies(
  //   restrictedAreaForm.value.operationStrategy
  // );
  restrictedAreaModalVisible.value = false;
  resetRestrictedAreaForm();
}
@@ -3802,6 +3933,18 @@
  if (value) showCreateMenu.value = false;
});
watch(isFenceGeometryLocked, (locked) => {
  if (!locked || !isFenceFormMode.value) return;
  const selectedRestrictedAreaId = activeRestrictedAreaId.value;
  syncLockedFenceReadonlySelection(selectedRestrictedAreaId);
  if (!selectedRestrictedAreaId) return;
  const currentArea = fenceRestrictedAreas.value.find(
    (item) => item.id === selectedRestrictedAreaId
  );
  if (currentArea?.polygon && map.value)
    map.value.setFitView([currentArea.polygon], false, [120, 120, 120, 120]);
});
async function handleMapEntityClick(type: WorkbenchTab, id: string) {
  if (
    (activeTab.value !== type || selectedId.value !== id) &&
@@ -4695,6 +4838,17 @@
function handleMapCanvasClick(event: any) {
  const lnglat = event?.lnglat;
  if (!lnglat) return;
  if (
    activeTab.value === "fence" &&
    isFenceFormMode.value &&
    fenceMouseTool.value &&
    fenceInteractionMode.value === "restricted-draw"
  ) {
    setTimeout(() => {
      refreshRestrictedDrawingVertexCount();
      enforceRestrictedDrawingVertexLimit();
    }, 0);
  }
  if (suppressFenceEditGuideDismissOnce.value) return;
  dismissFenceEditGuide();
  if (
@@ -6799,6 +6953,18 @@
              </div>
            </div>
            <div
              v-if="showRestrictedVertexCounter"
              class="restricted-vertex-counter"
            >
              <span class="restricted-vertex-counter__label">顶点数量</span>
              <span class="restricted-vertex-counter__value"
                >{{ currentRestrictedVertexCount }}/{{
                  restrictedAreaVertexLimit
                }}</span
              >
            </div>
            <div class="tool-menu-wrapper coverage">
              <div
                class="operate-item"