| | |
| | | () => !fenceCreateMode.value && Number(fenceDetailData.value?.status) === 2 |
| | | ); |
| | | |
| | | // 围栏保存前的统一阻断原因。 |
| | | // 这里不直接执行 message,而是返回首个阻断文案,供按钮禁用态和保存提示复用同一套校验结果。 |
| | | const fenceSaveBlockReason = computed(() => { |
| | | if (!isFenceFormMode.value) return ""; |
| | | if (isLockedFenceStatusEdit.value) return ""; |
| | |
| | | if (Number(strategy.type) === 3 && !strategy.typeStr.length) |
| | | return `请为 ${strategy.name || "策略组"} 选择生效星期`; |
| | | let typeKey = ""; |
| | | // 非自定义策略按固定类型去重;自定义策略按“星期组合”去重。 |
| | | // 这样既能防止出现两个“工作日”策略,也允许不同星期组合并存。 |
| | | if (Number(strategy.type) === 0) typeKey = "workday"; |
| | | else if (Number(strategy.type) === 1) typeKey = "statutoryHoliday"; |
| | | else if (Number(strategy.type) === 2) typeKey = "holiday"; |
| | |
| | | if (typeKey && strategyTypeKeys.has(typeKey)) |
| | | return `运营策略存在重复:${strategy.name || "策略组"} 的类型已存在`; |
| | | if (typeKey) strategyTypeKeys.add(typeKey); |
| | | // “每天”覆盖全部日期范围,因此不允许再和其他策略组并存。 |
| | | if (typeKey.includes("custom-0") && fenceStrategyList.value.length > 1) |
| | | return "选择“每天”时,不能添加其他运营策略"; |
| | | if (!strategy.time.length) |
| | |
| | | } |
| | | for (const area of fenceRestrictedAreas.value) { |
| | | if (!area.areaName.trim()) return "请填写禁行区名称"; |
| | | // 禁行区只要开始绘制,就必须形成完整 polygon,避免保存半成品区域。 |
| | | if (area.points.length > 0 && area.points.length < 3) |
| | | return `${area.areaName || "禁行区"} 还未完成绘制`; |
| | | if (!area.points.length) return `${area.areaName || "禁行区"} 还未完成绘制`; |
| | |
| | | return ""; |
| | | }); |
| | | |
| | | // 场站在“自定义时间”模式下的时间段校验。 |
| | | // 这里和围栏策略校验拆开,便于场站表单保存、联动校验及错误提示单独复用。 |
| | | function getStationCustomTimeErrorMessage() { |
| | | if (stationForm.value.operationType !== "2") return ""; |
| | | const ranges = Array.isArray(stationForm.value.customTimeRanges) |
| | |
| | | return ""; |
| | | } |
| | | |
| | | // 编辑态下通过快照判断场站表单是否被修改,用于离开编辑态时提示是否放弃更改。 |
| | | const stationEditPayloadSnapshot = computed(() => { |
| | | return JSON.stringify({ |
| | | stationName: stationForm.value.stationName.trim(), |
| | |
| | | }); |
| | | }); |
| | | |
| | | // 场站保存前的统一阻断原因。 |
| | | // 与围栏类似,返回首个错误文案,供按钮禁用态、保存前提示、局部联动共用。 |
| | | const stationSaveBlockReason = computed(() => { |
| | | if (!isStationFormMode.value) return ""; |
| | | if (!stationForm.value.stationName.trim()) return "请输入场站名称"; |
| | |
| | | allowOutsideOperationArea: isStationEditWorkflow.value, |
| | | }); |
| | | if (geometryError) return geometryError; |
| | | // 编辑已有场站时允许它暂时落在围栏外,方便修复历史数据或承接围栏变更后的残留场站; |
| | | // 但只要当前没有匹配到任何运营区,就不能把状态切回“启用”。 |
| | | if ( |
| | | isStationEditWorkflow.value && |
| | | !resolveStationOperationAreaByPolygon(stationDraftPoints.value) && |
| | |
| | | .filter((item) => item.points.length >= 3); |
| | | } |
| | | |
| | | // 根据整个场站 polygon 反查所属运营区。 |
| | | // 这里不使用中心点命中,而是要求“场站整体完全落在某个运营区内”, |
| | | // 以便和保存校验、围栏解绑逻辑保持一致。 |
| | | function resolveStationOperationAreaByPolygon(points: LngLatTuple[]) { |
| | | if (points.length < 3) return null; |
| | | return ( |
| | |
| | | ); |
| | | } |
| | | |
| | | // 统一处理场站几何合法性校验: |
| | | // 1. 必须构成合法 polygon |
| | | // 2. 新建时必须在运营区内,编辑时允许临时落在围栏外 |
| | | // 3. 不允许与其他场站重叠/包含 |
| | | // 4. 不允许与启用中的禁行区重叠 |
| | | function getStationGeometryInvalidReason( |
| | | points: LngLatTuple[], |
| | | options: { |
| | |
| | | const otherStations = getOtherStations(); |
| | | const geometryUtil = (window as any).AMap?.GeometryUtil; |
| | | if (geometryUtil) { |
| | | // 先拦截相交,再拦截包含/被包含,提示更符合用户的实际绘制问题。 |
| | | const crossStation = otherStations.find((station) => |
| | | geometryUtil.doesRingRingIntersect(points, station.points) |
| | | ); |
| | |
| | | return ""; |
| | | } |
| | | |
| | | // 地图交互层的场站几何校验封装。 |
| | | // 与纯函数校验分开,专门负责在用户绘制/拖拽时弹出 warning。 |
| | | function validateStationGeometry(points: LngLatTuple[]) { |
| | | const errorMessage = getStationGeometryInvalidReason(points, { |
| | | allowOutsideOperationArea: isStationEditWorkflow.value, |
| | |
| | | return true; |
| | | } |
| | | |
| | | // 按场站 polygon 自动同步框选到的点位列表。 |
| | | // 保留已匹配点位的原有顺序,只把新增命中的点位追加到末尾,避免用户编辑时列表频繁跳动。 |
| | | function syncStationPointListByPolygon(path: LngLatTuple[]) { |
| | | const nextMatchedList = getPointItemsWithinPolygon(path).map( |
| | | mapListItemToStationPoint |
| | |
| | | }); |
| | | } |
| | | |
| | | // 基于当前草稿点位重建地图上的场站 polygon。 |
| | | // 只负责地图图形本身,不负责同步表单/点位列表,避免图形重建时产生额外副作用。 |
| | | function createStationDraftPolygon(points: LngLatTuple[]) { |
| | | if (!map.value || points.length < 3) return; |
| | | if (stationDraftPolygon.value) map.value.remove(stationDraftPolygon.value); |
| | |
| | | map.value.add(stationDraftPolygon.value); |
| | | } |
| | | |
| | | // 首次应用一组完整的场站草稿几何信息。 |
| | | // 用于详情回显、新建绘制完成后落稿,同时按最新 polygon 自动同步场站内点位。 |
| | | function applyStationDraftGeometry(points: LngLatTuple[], fitView = false) { |
| | | stationDraftPoints.value = clonePolygonPath(points); |
| | | stationForm.value.polygonPath = clonePolygonPath(points); |
| | |
| | | map.value.setFitView([stationDraftPolygon.value], false, [100, 100, 100, 100]); |
| | | } |
| | | |
| | | // 编辑器拖拽/加点/删点过程中的轻量同步。 |
| | | // 这里只更新内存态,不重建 polygon,避免编辑过程出现闪烁。 |
| | | function syncStationDraftGeometryState(points: LngLatTuple[]) { |
| | | stationDraftPoints.value = clonePolygonPath(points); |
| | | stationForm.value.polygonPath = clonePolygonPath(points); |
| | | syncStationPointListByPolygon(points); |
| | | } |
| | | |
| | | // 当编辑后的几何不合法时,回滚到上一次合法草稿,并重新挂回编辑器。 |
| | | // 这里通过 rollbacking 标记和 suppress 标记避免 close/open 编辑器时触发重复 end 事件。 |
| | | async function rollbackStationDraftGeometry() { |
| | | if (!map.value || stationDraftPoints.value.length < 3 || stationEditorRollbacking) |
| | | return; |
| | |
| | | enableStationDraftEdit(); |
| | | } |
| | | |
| | | // 打开场站 polygon 的可编辑态,并把 AMap 编辑事件同步回表单草稿。 |
| | | // adjust/addnode/removenode 过程中先实时校验,若非法则立即回滚,确保草稿始终保持可保存状态。 |
| | | function enableStationDraftEdit() { |
| | | if (!map.value || !stationDraftPolygon.value || stationEditorRollbacking) return; |
| | | if (stationPolyEditor.value) suppressStationEditorEnd.value = true; |
| | |
| | | editOptions: { fillColor: "#A034FF" }, |
| | | } |
| | | ); |
| | | // 统一从 polygon 实例回读最新 path,避免不同编辑事件各自维护一套取值逻辑。 |
| | | const syncStationGeometry = () => { |
| | | if (!stationDraftPolygon.value || stationEditorRollbacking) return; |
| | | const points = stationDraftPolygon.value |
| | |
| | | } |
| | | syncStationDraftGeometryState(points); |
| | | }; |
| | | // 主动 close 编辑器时会触发 end,这里用 suppress 标记过滤掉非用户操作的结束事件。 |
| | | const handleStationGeometryEditEnd = () => { |
| | | if (suppressStationEditorEnd.value) { |
| | | suppressStationEditorEnd.value = false; |