| src/service/api/testOperationWorkbench.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/businessOperations/operationWorkbench/map.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/components/FencePanel.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/map.definition.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/map.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/status.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/workbench.constants.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/workbench.fence-workflow.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/testDemo/operationWorkbench/workbench.payload.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/service/api/testOperationWorkbench.ts
@@ -10,19 +10,19 @@ } export function getFenceList(params: any) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/electronicFence/pageList', params) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/electronicFence/pageListDemo', params) } export function addFence(params: any) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/electronicFence/add', params) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/electronicFence/addDemo', params) } export function copyFence(id: any) { return request.Post<Service.ResponseResult<any>>(`/map/api/v1/electronicFence/copy?id=${id}`) return request.Post<Service.ResponseResult<any>>(`/operation/api/v1/electronicFence/copyDemo?id=${id}`) } export function editFence(params: any) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/electronicFence/edit', params) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/electronicFence/editDemo', params) } export function validateFenceOperationStrategyShorten(params: { @@ -30,47 +30,47 @@ operationStrategy: string }) { return request.Post<Service.ResponseResult<any>>( '/map/api/v1/electronicFence/validateOperationStrategyShorten', '/operation/api/v1/electronicFence/validateOperationStrategyShortenDemo', params, ) } export function getFenceDetail(id: any) { return request.Post<Service.ResponseResult<any>>(`/map/api/v1/electronicFence/queryById?id=${id}`) return request.Post<Service.ResponseResult<any>>(`/operation/api/v1/electronicFence/queryByIdDemo?id=${id}`) } /** 点位相关 */ export function fetchPointPageList(params: any) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/point/pageList', params) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/point/pageListDemo', params) } export function fetchPointList(params: Api.Point.ListParams) { return request.Post<Service.ResponseResult<Entity.Point[]>>('/map/api/v1/point/list', params) return request.Post<Service.ResponseResult<Entity.Point[]>>('/map/api/v1/point/listDemo', params) } export function createPoint(data: Api.Point.CreateData) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/point/add', data) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/point/addDemo', data) } export function updatePoint(data: Api.Point.UpdateData) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/point/edit', data) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/point/editDemo', data) } export function enableDisablePoint(data: Api.Point.EnableDisableData) { return request.Post<Service.ResponseResult<any>>('/map/api/v1/point/enableDisable', data) return request.Post<Service.ResponseResult<any>>('/operation/api/v1/point/enableDisableDemo', data) } export function fetchPointDetail(id: number | string) { return request.Get<Service.ResponseResult<Entity.Point>>('/map/api/v1/point/queryById', { params: { id } }) return request.Get<Service.ResponseResult<Entity.Point>>('/operation/api/v1/point/queryByIdDemo', { params: { id } }) } export function deletePoint(id: number | string) { return request.Get<Service.ResponseResult<any>>(`/map/api/v1/point/deletePoint?ids=${id}`) return request.Get<Service.ResponseResult<any>>(`/operation/api/v1/point/deletePointDemo?ids=${id}`) } export function fetchElectronicFenceAreaList(params: { cityCode?: string } = {}) { return request.Post<Service.ResponseResult<Entity.ElectronicFenceArea[]>>('/map/api/v1/point/listElectronicFenceArea', params) return request.Post<Service.ResponseResult<Entity.ElectronicFenceArea[]>>('/operation/api/v1/point/listElectronicFenceAreaDemo', params) } export function fetchFenceAndStationAreaList(params: { cityCode?: string } = {}) { @@ -78,11 +78,11 @@ type: number name: string ids: number | string }>>>('/map/api/v1/electronicFence/findListFenceAndStation', { params }) }>>>('/operation/api/v1/electronicFence/findListFenceAndStationDemo', { params }) } export function exportPointExcel(data: { pointIds: (number | string)[] }) { const method = request.Post<Blob>('/map/api/v1/point/exportExcel', data, { responseType: 'blob' }) const method = request.Post<Blob>('/operation/api/v1/point/exportExcelDemo', data, { responseType: 'blob' }) method.meta = { ...method.meta, isBlob: true } return method } src/views/businessOperations/operationWorkbench/map.vue
@@ -5648,7 +5648,6 @@ pointForm.value.pointOnOff = pointOnOff; pointSaving.value = true; try { const isCreatingPoint = pointCreateMode.value; const basePayload = buildPointSavePayload({ administrativeInfo: normalizedAdministrativeInfo, pointForm: pointForm.value, @@ -5676,9 +5675,6 @@ if (res.code === 200) { workbenchStore.markListNeedRefresh(); window.$message?.success("保存成功"); const createdPointNumber = isCreatingPoint ? String(res?.data?.pointNumber || (res as any)?.pointNumber || "") : ""; pointEditMode.value = false; pointCreateMode.value = false; pointCreateReady.value = false; @@ -5686,19 +5682,22 @@ selectedCreateTool.value = ""; cleanupPointEditMapState(); await loadPointResources(); if (createdPointNumber) { const createdItem = pointData.value.find( (item) => String(item.raw?.pointNumber || "") === createdPointNumber ); if (createdItem) { pointPanelClosed.value = false; selectedId.value = createdItem.id; await loadPointDetailData(String(createdItem.id)); return true; } } if (selectedId.value) await loadPointDetailData(String(selectedId.value)); // 正式工作台保存后统一关闭右侧详情,避免继续展示可能已失效的详情数据。 const workflowState = buildPointClosePanelState({ isCreateMode: false, detail: pointDetailData.value, fallbackPointPictures: selectedItem.value?.raw?.pointPictures, defaultPointImage: DEFAULT_POINT_IMAGE, }); pointPanelClosed.value = workflowState.pointPanelClosed; selectedId.value = workflowState.selectedId; pointDetailData.value = workflowState.pointDetailData; pointForm.value = workflowState.form; pointCreateStatusState.value = workflowState.pointCreateStatus; lastAutoFilledPointName.value = workflowState.lastAutoFilledPointName; pointAddressQuery.value = workflowState.addressQuery; pointAddressOptions.value = workflowState.addressOptions; uploadedImages.value = workflowState.uploadedImages; return true; } return false; @@ -5764,9 +5763,10 @@ window.$message?.warning("未找到运营区信息,暂无法编辑"); return; } const hasGeometryChanged = !fenceCreateMode.value && hasFenceGeometryChanged(); if ( !fenceCreateMode.value && hasFenceGeometryChanged() && hasGeometryChanged && !(await openConfirmDialog( "提示", "已修改围栏边界或禁行区边界,保存后围栏将重回测试状态,确定保存吗?", @@ -5784,6 +5784,7 @@ const isCreatingFence = fenceCreateMode.value; const shouldConfirmStatusChange = saveType === 1 && !hasGeometryChanged && !fenceCreateMode.value && fenceForm.value.areaStatus !== getFenceOriginalAreaStatus(); if (shouldConfirmStatusChange) { @@ -5799,6 +5800,7 @@ fenceStrategyList.value ); if ( !hasGeometryChanged && !fenceCreateMode.value && !(await confirmFenceOperationStrategyShortenEdit( String(fenceDetailData.value.id), @@ -5809,13 +5811,15 @@ return; } const fencePointRelationPayload = saveType === 1 // 围栏边界变更后的状态和关联影响交由编辑接口统一处理; // 前端只保留一次边界变更确认,确认后直接继续保存。 saveType === 1 && !hasGeometryChanged ? await resolveFencePointRelationPayload() : { addPointIdList: [], delPointIdList: [], disablePointIdList: [] }; const disabledPointCount = fencePointRelationPayload.disablePointIdList.length; const fenceSubmitConfirmMessage = saveType === 1 saveType === 1 && !hasGeometryChanged ? buildFenceSubmitConfirmMessage(disabledPointCount) : ""; if (fenceSubmitConfirmMessage) { @@ -5851,24 +5855,19 @@ if (res.code === 200) { workbenchStore.markListNeedRefresh(); window.$message?.success( saveType === 0 ? "保存草稿成功" : isCreatingFence ? "添加成功" : "编辑成功" saveType === 0 ? "保存草稿成功" : "编辑成功" ); const currentFenceId = String(fenceDetailData.value?.id || ""); fenceEditMode.value = false; fenceCreateMode.value = false; fenceEditSnapshot.value = ""; selectedCreateTool.value = ""; cleanupFenceEditorState(); await Promise.all([ loadFenceData(), !fenceCreateMode.value && currentFenceId ? loadFenceDetailData(currentFenceId) : Promise.resolve(), ]); await loadFenceData(); // 正式工作台保存后统一关闭右侧详情,避免继续展示可能已失效的详情数据。 const workflowState = buildFenceClosePanelState(); fencePanelClosed.value = workflowState.fencePanelClosed; selectedId.value = ""; fenceDetailData.value = null; renderMapEntities(); return; } src/views/testDemo/operationWorkbench/components/FencePanel.vue
@@ -8,7 +8,6 @@ NInput, NSelect, NSwitch, NTag, NTimePicker, } from "naive-ui"; import type { FormInst, FormItemRule } from "naive-ui"; @@ -23,7 +22,7 @@ import delIcon from "@/assets/geofence/del.png"; import noEntryIcon from "@/assets/geofence/noEntry.png"; import resetPointIcon from "@/assets/point/reset-point.png"; import { getStatusClass } from "../status"; import { getFenceFlowStatusText, getStatusClass } from "../status"; const props = defineProps<{ editMode: boolean; @@ -34,9 +33,9 @@ strategyList: Array<Record<string, any>>; detailRows: Array<{ label: string; value: string }>; fenceDetailData: Record<string, any> | null; currentFenceTestStatusText: string; currentFenceFlowStatusText: string; statusMeta: { testStatus: number; flowStatus: number; }; buttonConfig: { showVerifyButtons: boolean; @@ -98,18 +97,29 @@ { label: "星期日", value: 7 }, ]; // 测试工作台里 flowStatus=5 代表围栏已进入运营态,此时面板仅保留详情展示。 const isLocked = computed( () => !props.createMode && Number(props.fenceDetailData?.status) === 2 ); const isAreaStatusSwitchDisabled = computed( () => !props.createMode && [0, 1].includes(Number(props.fenceDetailData?.status)) !props.createMode && Number( props.fenceDetailData?.flowStatus || props.statusMeta?.flowStatus || props.form?.flowStatus || 1 ) === 5 ); const showDraftButton = computed( () => !!props.createMode || Number(props.fenceDetailData?.status) === 0 ); const currentFenceTestStatusClass = computed(() => getStatusClass(props.currentFenceTestStatusText) getStatusClass(props.currentFenceFlowStatusText) ); const currentFenceStepStatusText = computed(() => getFenceFlowStatusText( props.statusMeta?.flowStatus || props.fenceDetailData?.flowStatus || props.form?.flowStatus ) ); const operationArea = computed( () => props.areaSummary.operationAreas?.[0] || null @@ -241,7 +251,15 @@ return true; if (value == null) return new Error("请选择生效时间"); if (typeof value === "number") { if (!props.createMode && Number(props.fenceDetailData?.status) === 2) if ( !props.createMode && Number( props.fenceDetailData?.flowStatus || props.statusMeta?.flowStatus || props.form?.flowStatus || 1 ) === 5 ) return true; const now = new Date(); const minAllowedDateTime = new Date( @@ -286,20 +304,6 @@ }, } as FormItemRule, }; function getFenceStatusText(status: number) { if (Number(status) === 0) return "草稿"; if (Number(status) === 1) return "待生效"; if (Number(status) === 2) return "启用中"; if (Number(status) === 3) return "已停用"; return "-"; } function getFenceStatusTagType(status: number) { if (Number(status) === 2) return "success"; if (Number(status) === 1) return "warning"; return "default"; } function getStrategyTypeText(item: Record<string, any>) { if (Number(item?.type) === 3) { @@ -486,14 +490,6 @@ <div class="main-head" :class="{ 'sticky-top': showDrawerHead }"> <div v-if="createMode" class="flex items-center gap-4px mb-16px"> <div class="title">围栏详情</div> <n-switch v-model:value="form.areaStatus" :checked-value="1" :unchecked-value="2" > <template #checked> 启用 </template> <template #unchecked> 停用 </template> </n-switch> <img :src="closeIcon" class="w-24px h-24px ml-auto cursor-pointer" @@ -508,26 +504,9 @@ form.electronicName || "围栏详情" }} <NTag style="font-weight: 500" :type=" getFenceStatusTagType(Number(fenceDetailData?.status || 0)) " :bordered="false" class="h-24px mx-4px" > {{ getFenceStatusText(Number(fenceDetailData?.status || 0)) }} </NTag> <n-switch v-model:value="form.areaStatus" :checked-value="1" :unchecked-value="2" :disabled="isAreaStatusSwitchDisabled" class="text-13px" > <template #checked> 启用 </template> <template #unchecked> 停用 </template> </n-switch> <span class="fence-test-status-tag mx-4px" :class="currentFenceTestStatusClass"> {{ currentFenceFlowStatusText || "-" }} </span> </div> <img :src="closeIcon" @@ -593,7 +572,7 @@ <div class="box-title mb-8px">围栏测试状态</div> <div class="fence-test-status-wrapper"> <PointStatusSteps :point-status="currentFenceTestStatusText || '待测试'" :point-status="currentFenceStepStatusText || '待测试'" :custom-steps="['待测试', '测试中', '可运营']" error-step-label="测试不通过" /> @@ -631,7 +610,7 @@ </div> <div class="verify-btn pass" @click="emit('verify-status-update', 3)" @click="emit('verify-status-update', 5)" > 测试通过 </div> @@ -1019,16 +998,9 @@ <div class="flex items-center gap-4px mb-4px"> <div v-if="fenceDetailData" class="title items-center"> {{ fenceDetailData.electronicName || "" }} <NTag style="font-weight: 500" :type=" getFenceStatusTagType(Number(fenceDetailData.status || 0)) " :bordered="false" class="h-24px mx-4px" > {{ getFenceStatusText(Number(fenceDetailData.status || 0)) }} </NTag> <span class="fence-test-status-tag mx-4px" :class="currentFenceTestStatusClass"> {{ currentFenceFlowStatusText || "-" }} </span> </div> <img :src="closeIcon" @@ -1073,17 +1045,6 @@ <div class="box flex flex-col"> <div class="box-title mb-8px">生命周期</div> <div class="box-item mb-8px"> <div class="label">围栏测试状态</div> <div class="value"> <span class="fence-test-status-tag" :class="currentFenceTestStatusClass" > {{ currentFenceTestStatusText || "-" }} </span> </div> </div> <div class="box-item mb-8px"> <div class="label">生效时间</div> <div class="value"> @@ -1234,7 +1195,8 @@ </div> </div> </div> <div class="drawer-footer"> <!-- 运营围栏在测试工作台只允许查看详情,不展示底部编辑入口。 --> <div v-if="!isLocked" class="drawer-footer"> <NButton type="primary" class="w-full" @click="emit('edit-current')"> 编辑 </NButton> @@ -1610,6 +1572,7 @@ font-size: 12px; line-height: 16px; color: #0068ff; font-weight: 400; background: #e6f4ff; &.wait-test { @@ -1631,6 +1594,11 @@ background: #e6f4ff; color: #0068ff; } &.test-stop { background: #e8e9e9; color: #171f26; } } .fence-inner-stats { src/views/testDemo/operationWorkbench/map.definition.ts
@@ -79,7 +79,7 @@ id: string; electronicName: string; areaDescription: string; testStatus: number; flowStatus: number; areaType: number; areaStatus: number; effectType: number; src/views/testDemo/operationWorkbench/map.vue
@@ -42,7 +42,6 @@ clonePointTimeRanges, formatFenceListDesc, formatStationListDesc, getFenceStatusText, mapListItemToStationPoint, mapPointApiItemToListItem, mapStationApiItemToListItem, @@ -126,7 +125,8 @@ } from "./workbench.station-workflow"; import { buildPointStatusMetricSummary, getFenceTestStatusText, getFenceFlowStatusText, getFenceWorkbenchStatusText, buildStationStatusMetricSummary, getPointStatusText, getStatusClass, @@ -417,7 +417,6 @@ (document.activeElement as HTMLElement | null)?.blur?.(); } const pointCreateStatusState = ref(createDefaultPointCreateStatus()); const fenceLocalTestStatusMap = ref<Record<string, number>>({}); // 点位编辑弹窗与地址搜索相关状态,供地图临时点位和右侧表单共用。 const pointAddressQuery = ref(""); @@ -445,6 +444,7 @@ const fenceDraftPolygon = shallowRef<any>(null); const fenceDraftRestrictedPolygons = shallowRef<any[]>([]); const fenceDraftPoints = ref<LngLatTuple[]>([]); const lastValidFenceDrawingPoints = ref<LngLatTuple[]>([]); const restrictedDrawingVertexCount = ref(0); const fenceRestrictedAreas = ref<FenceRestrictedAreaDraft[]>([]); const activeRestrictedAreaId = ref(""); @@ -455,6 +455,7 @@ const restrictedAreaEditorClosed = ref(false); const suppressRestrictedEditorEnd = ref(false); let restrictedEditorRollbacking = false; let fenceEditorRollbacking = false; const restrictedAreaModalVisible = ref(false); const fencePanelRef = ref<any>(null); const restrictedAreaModalRef = ref<any>(null); @@ -744,26 +745,25 @@ ); }); function normalizeFenceTestStatus(value?: number | string | null) { function normalizeFenceFlowStatus(value?: number | string | null) { const status = Number(value); return [1, 2, 3, 4].includes(status) ? status : 1; return [1, 2, 3, 4, 5].includes(status) ? status : 1; } function resolveFenceLocalTestStatus(detail?: any) { const fenceId = String(detail?.id || selectedId.value || ""); if (fenceId) { const localStatus = fenceLocalTestStatusMap.value[fenceId]; if ([1, 2, 3, 4].includes(Number(localStatus))) return Number(localStatus); } return normalizeFenceTestStatus(detail?.testStatus); function resolveFenceLocalFlowStatus(detail?: any) { return normalizeFenceFlowStatus(detail?.flowStatus); } const currentFenceTestStatusText = computed(() => { const currentFenceFlowStatusText = computed(() => { const currentStatus = fenceDetailData.value?.status; if (fenceEditMode.value || fenceCreateMode.value) return getFenceTestStatusText(fenceForm.value.testStatus); if (activeTab.value !== "fence") return getFenceTestStatusText(); return getFenceTestStatusText(resolveFenceLocalTestStatus(fenceDetailData.value)); return getFenceWorkbenchStatusText(fenceForm.value.flowStatus, currentStatus); if (activeTab.value !== "fence") return getFenceWorkbenchStatusText(undefined, currentStatus); return getFenceWorkbenchStatusText( resolveFenceLocalFlowStatus(fenceDetailData.value), currentStatus ); }); // 测试运营工作台的点位编辑仅保留测试流程,不再按运营态额外锁定位置编辑。 @@ -841,10 +841,12 @@ const isFenceEditWorkflow = computed( () => fenceEditMode.value && !fenceCreateMode.value ); // 测试工作台不继承“启用中不可操作”的旧规则;这里只保留运营围栏的兜底锁, // 防止异常情况下进入编辑态后还能继续拖拽围栏几何。 const isFenceGeometryLocked = computed( () => isFenceEditWorkflow.value && Number(fenceForm.value.areaStatus || fenceDetailData.value?.areaStatus || 1) === 1 resolveFenceLocalFlowStatus(fenceDetailData.value) === 5 ); const isStationFormMode = computed( () => @@ -1103,10 +1105,10 @@ }); const fenceButtonConfig = computed(() => { const testStatus = const flowStatus = fenceCreateMode.value || fenceEditMode.value ? normalizeFenceTestStatus(fenceForm.value.testStatus) : resolveFenceLocalTestStatus(fenceDetailData.value); ? normalizeFenceFlowStatus(fenceForm.value.flowStatus) : resolveFenceLocalFlowStatus(fenceDetailData.value); if (!fenceCreateMode.value && !fenceDetailData.value) return { showVerifyButtons: false, @@ -1114,9 +1116,9 @@ showNextStep: false, }; return { showVerifyButtons: testStatus === 2, showRestartButton: testStatus === 4, showNextStep: testStatus === 1, showVerifyButtons: flowStatus === 2, showRestartButton: flowStatus === 4, showNextStep: flowStatus === 1, }; }); @@ -1158,7 +1160,7 @@ return JSON.stringify({ electronicName: fenceForm.value.electronicName.trim(), areaDescription: fenceForm.value.areaDescription.trim(), testStatus: normalizeFenceTestStatus(fenceForm.value.testStatus), flowStatus: normalizeFenceFlowStatus(fenceForm.value.flowStatus), areaStatus: fenceForm.value.areaStatus, effectType: fenceForm.value.effectType, effectTime: fenceForm.value.effectTime, @@ -1195,7 +1197,9 @@ }); const isLockedFenceStatusEdit = computed( () => !fenceCreateMode.value && Number(fenceDetailData.value?.status) === 2 () => !fenceCreateMode.value && resolveFenceLocalFlowStatus(fenceDetailData.value) === 5 ); const fenceSaveBlockReason = computed(() => { @@ -1804,6 +1808,7 @@ activeRestrictedAreaId.value = ""; readonlyRestrictedAreaId.value = ""; fenceDraftPoints.value = []; lastValidFenceDrawingPoints.value = []; restrictedDrawingVertexCount.value = 0; invalidFenceStationIds.value = []; updateFenceStationWarningStyles(); @@ -1819,10 +1824,55 @@ restrictedDrawingVertexCount.value = getRestrictedDrawingVertexCount(); } function applyRestrictedPolygonPath(polygon: any, points: LngLatTuple[]) { function applyPolygonPath(polygon: any, points: LngLatTuple[]) { polygon?.setPath?.( points.map(([lng, lat]) => new (window as any).AMap.LngLat(lng, lat)) ); } // 围栏新建打点时,MouseTool 临时路径的最后一个点是跟随鼠标移动的预览点, // 这里裁掉尾点,仅保留用户已经确认的顶点做实时几何校验。 function getFenceDrawingConfirmedPoints() { const path = fenceMouseTool.value?.polygonTemp?.obj?.getPath?.(); if (!Array.isArray(path) || !path.length) return []; const confirmedPath = path.length > 1 ? path.slice(0, -1) : path; return confirmedPath.map( (point: any) => [point.lng, point.lat] as LngLatTuple ); } function applyFenceTempPolygonPath( polygon: any, points: LngLatTuple[], trailingPoint?: any ) { const nextPath = points.map( ([lng, lat]) => new (window as any).AMap.LngLat(lng, lat) ); if (trailingPoint) nextPath.push(trailingPoint); polygon?.setPath?.(nextPath); } // 围栏打点阶段一旦形成自相交,立即回退到上一个合法点集,而不是等到双击闭合后再报错。 function enforceFenceDrawingSelfIntersection() { const tempPolygon = fenceMouseTool.value?.polygonTemp?.obj; const path = tempPolygon?.getPath?.(); if (!Array.isArray(path) || !path.length) return false; const nextPoints = getFenceDrawingConfirmedPoints(); if (nextPoints.length < 4) { lastValidFenceDrawingPoints.value = clonePolygonPath(nextPoints); return false; } if (!isFencePolygonSelfIntersect(nextPoints)) { lastValidFenceDrawingPoints.value = clonePolygonPath(nextPoints); return false; } const fallbackPoints = lastValidFenceDrawingPoints.value.length ? lastValidFenceDrawingPoints.value : nextPoints.slice(0, -1); applyFenceTempPolygonPath(tempPolygon, fallbackPoints, path[path.length - 1]); window.$message?.warning("运营区域不允许自相交"); return true; } function enforceRestrictedDrawingVertexLimit() { @@ -1956,16 +2006,38 @@ } ); fencePolyEditor.value.open(); const syncFencePoints = () => { fenceDraftPoints.value = fenceDraftPolygon.value .getPath() .map((point: any) => [point.lng, point.lat] as LngLatTuple); let lastValidPoints = clonePolygonPath(fenceDraftPoints.value); const syncFenceDraftPointState = (points: LngLatTuple[]) => { fenceDraftPoints.value = clonePolygonPath(points); syncFenceStationWarningState(fenceDraftPoints.value); renderFenceEditGuide(); logPolygonMatchedPoints( "fence-preview", getPointItemsWithinPolygon(fenceDraftPoints.value, pointSpatialData.value) ); }; const syncFencePoints = () => { const nextPoints = fenceDraftPolygon.value .getPath() .map((point: any) => [point.lng, point.lat] as LngLatTuple); // 创建完成后的拖点、加点、删点同样实时校验自相交,非法变更立即回滚。 if ( nextPoints.length >= 4 && lastValidPoints.length >= 3 && isFencePolygonSelfIntersect(nextPoints) && !fenceEditorRollbacking ) { fenceEditorRollbacking = true; window.$message?.warning("运营区域不允许自相交"); applyPolygonPath(fenceDraftPolygon.value, lastValidPoints); syncFenceDraftPointState(lastValidPoints); setTimeout(() => { fenceEditorRollbacking = false; }, 0); return; } syncFenceDraftPointState(nextPoints); if (nextPoints.length >= 3) lastValidPoints = clonePolygonPath(nextPoints); }; fencePolyEditor.value.on("end", syncFencePoints); fencePolyEditor.value.on("adjust", syncFencePoints); @@ -1977,6 +2049,7 @@ function startFencePolygonDraw() { if (!map.value || typeof (window as any).AMap === "undefined") return; cleanupFenceEditorState(); lastValidFenceDrawingPoints.value = []; fenceMouseTool.value = new (window as any).AMap.MouseTool(map.value); fenceMouseTool.value.polygon({ strokeColor: "#F19216", @@ -2431,7 +2504,7 @@ window.$message?.warning( `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位` ); applyRestrictedPolygonPath(polygon, lastValidPoints); applyPolygonPath(polygon, lastValidPoints); restrictedDrawingVertexCount.value = lastValidPoints.length; setTimeout(() => { restrictedEditorRollbacking = false; @@ -4869,6 +4942,16 @@ enforceRestrictedDrawingVertexLimit(); }, 0); } if ( activeTab.value === "fence" && isFenceFormMode.value && fenceMouseTool.value && fenceInteractionMode.value !== "restricted-draw" ) { setTimeout(() => { enforceFenceDrawingSelfIntersection(); }, 0); } if (suppressFenceEditGuideDismissOnce.value) return; dismissFenceEditGuide(); if ( @@ -4903,7 +4986,7 @@ // ===== 围栏详情态与编辑态切换 ===== function resetFenceForm(detail?: any) { const workflowState = buildFenceWorkflowFormState(detail); workflowState.form.testStatus = resolveFenceLocalTestStatus(detail); workflowState.form.flowStatus = resolveFenceLocalFlowStatus(detail); fenceForm.value = workflowState.form; fenceStrategyList.value = workflowState.strategyList; if (workflowState.restrictedAreas) fenceRestrictedAreas.value = workflowState.restrictedAreas; @@ -4955,7 +5038,7 @@ fenceCreateMode.value = workflowState.fenceCreateMode; fenceEditMode.value = workflowState.fenceEditMode; fenceForm.value = workflowState.fenceForm; fenceForm.value.testStatus = 1; fenceForm.value.flowStatus = 1; fenceStrategyList.value = workflowState.fenceStrategyList; restrictedAreaForm.value = workflowState.restrictedAreaForm; fenceRestrictedAreas.value = workflowState.fenceRestrictedAreas; @@ -5150,11 +5233,16 @@ function handleEnterFenceEdit() { if (!fenceDetailData.value) return; // 运营围栏在测试工作台仅允许查看详情;即使列表接口异常返回,也在前端兜底拦截。 if (resolveFenceLocalFlowStatus(fenceDetailData.value) === 5) { window.$message?.warning("运营围栏不可编辑"); return; } const workflowState = buildFenceEditModeState(fenceDetailData.value); fenceCreateMode.value = workflowState.fenceCreateMode; armFenceEditGuide(); fenceForm.value = workflowState.fenceForm; fenceForm.value.testStatus = resolveFenceLocalTestStatus(fenceDetailData.value); fenceForm.value.flowStatus = resolveFenceLocalFlowStatus(fenceDetailData.value); fenceStrategyList.value = workflowState.fenceStrategyList; fenceEditMode.value = workflowState.fenceEditMode; enterFenceDraftByDetail(fenceDetailData.value); @@ -5395,19 +5483,11 @@ } } function applyLocalFenceTestStatus(testStatus: number) { const normalizedStatus = normalizeFenceTestStatus(testStatus); fenceForm.value.testStatus = normalizedStatus; if (fenceCreateMode.value) return; const fenceId = String(fenceDetailData.value?.id || selectedId.value || ""); if (!fenceId) return; fenceLocalTestStatusMap.value = { ...fenceLocalTestStatusMap.value, [fenceId]: normalizedStatus, }; if (fenceDetailData.value) fenceDetailData.value.testStatus = normalizedStatus; const listItem = fenceData.value.find((item) => item.id === fenceId); if (listItem) listItem.raw = { ...(listItem.raw || {}), testStatus: normalizedStatus }; function applyLocalFenceFlowStatus(flowStatus: number) { const normalizedStatus = normalizeFenceFlowStatus(flowStatus); fenceForm.value.flowStatus = normalizedStatus; // 围栏测试流转在编辑阶段只更新表单草稿; // 左右侧的最终状态展示仍以后端保存成功后的返回结果为准。 } function handleFenceVerifyStatusUpdate(status: number) { @@ -5418,11 +5498,11 @@ "测试不通过后,当前围栏将保持不可运营状态,确定吗?" ).then((ok) => { if (!ok) return; applyLocalFenceTestStatus(status); applyLocalFenceFlowStatus(status); }); return; } if (status === 3) { if (status === 5 || status === 3) { const overlapResult = findPolygonOverlapArea( fenceDraftPoints.value, getOtherOperationAreas() @@ -5435,13 +5515,15 @@ return; } } applyLocalFenceTestStatus(status); applyLocalFenceFlowStatus(status); } function handleFenceNextStep() { if (!fenceCreateMode.value && !fenceDetailData.value) return; if (normalizeFenceTestStatus(fenceForm.value.testStatus) !== 1) return; applyLocalFenceTestStatus(2); const currentStatus = normalizeFenceFlowStatus(fenceForm.value.flowStatus); if (currentStatus === 1) { applyLocalFenceFlowStatus(2); } } async function handleRestartFenceTest() { @@ -5453,7 +5535,7 @@ )) ) return; applyLocalFenceTestStatus(1); applyLocalFenceFlowStatus(1); } async function handleCancelFenceEdit() { @@ -5464,7 +5546,7 @@ } const workflowState = buildFenceCancelEditState(fenceDetailData.value); fenceForm.value = workflowState.fenceForm; fenceForm.value.testStatus = resolveFenceLocalTestStatus(fenceDetailData.value); fenceForm.value.flowStatus = resolveFenceLocalFlowStatus(fenceDetailData.value); fenceStrategyList.value = workflowState.fenceStrategyList; fenceEditMode.value = workflowState.fenceEditMode; fenceEditSnapshot.value = workflowState.fenceEditSnapshot; @@ -5735,9 +5817,9 @@ window.$message?.warning("未找到运营区信息,暂无法编辑"); return; } const hasGeometryChanged = !fenceCreateMode.value && hasFenceGeometryChanged(); if ( !fenceCreateMode.value && hasFenceGeometryChanged() && hasGeometryChanged && !(await openConfirmDialog( "提示", "已修改围栏边界或禁行区边界,保存后围栏将重回测试状态,确定保存吗?", @@ -5804,6 +5886,7 @@ fenceCreateMode: fenceCreateMode.value, fenceId: String(fenceDetailData.value?.id || ""), currentStatus: fenceDetailData.value?.status, hasGeometryChanged, cityValue: cityValue.value, fenceDetailCityCode: fenceDetailData.value?.cityCode, saveType, @@ -6044,16 +6127,17 @@ if (fenceRes.code === 200 && fenceRes.data?.records) { fenceData.value = fenceRes.data.records.map((item: any) => { const localTestStatus = resolveFenceLocalTestStatus(item); const localFlowStatus = resolveFenceLocalFlowStatus(item); return { id: String(item.id), name: item.electronicName || "-", status: getFenceStatusText(item.status), // 测试工作台左侧围栏状态优先看 flowStatus,只有 flowStatus=5 时才沿用旧主状态。 status: getFenceWorkbenchStatusText(localFlowStatus, item.status), desc: formatFenceListDesc(item), type: "fence", raw: { ...item, testStatus: localTestStatus, flowStatus: localFlowStatus, }, }; }); @@ -7262,9 +7346,9 @@ :strategy-list="fenceStrategyList" :detail-rows="detailRows" :fence-detail-data="fenceDetailData" :current-fence-test-status-text="currentFenceTestStatusText" :current-fence-flow-status-text="currentFenceFlowStatusText" :status-meta="{ testStatus: normalizeFenceTestStatus(fenceForm.testStatus), flowStatus: normalizeFenceFlowStatus(fenceForm.flowStatus), }" :button-config="fenceButtonConfig" :strategy-summary="fenceStrategySummary" src/views/testDemo/operationWorkbench/status.ts
@@ -19,13 +19,33 @@ return "待测试"; } export function getFenceTestStatusText(testStatus?: number | string) { const status = Number(testStatus); export function getFenceFlowStatusText(flowStatus?: number | string) { const status = Number(flowStatus); if (status === 2) return "测试中"; if (status === 3) return "可运营"; if (status === 3 || status === 5) return "可运营"; if (status === 4) return "测试不通过"; return "待测试"; } function getFenceOperationStatusText(status?: number | string) { const normalizedStatus = Number(status); if (normalizedStatus === 1) return "待生效"; if (normalizedStatus === 2) return "启用中"; if (normalizedStatus === 3) return "已停用"; return "草稿"; } // 测试工作台左侧围栏状态展示规则: // flowStatus=5 说明围栏已经进入运营态,此时沿用服务端主状态; // 其余情况统一展示测试流状态,并复用点位的状态颜色样式。 export function getFenceWorkbenchStatusText( flowStatus?: number | string, status?: number | string ) { return Number(flowStatus) === 5 ? getFenceOperationStatusText(status) : getFenceFlowStatusText(flowStatus); } export interface PointStatusMetricItem { @@ -167,7 +187,7 @@ if (["待测试"].includes(String(status || ""))) return "wait-test"; if (["测试中"].includes(String(status || ""))) return "testing"; if (["测试不通过"].includes(String(status || ""))) return "test-fail"; if (["可运营"].includes(String(status || ""))) return "test-pass"; if (["测试通过", "可运营"].includes(String(status || ""))) return "test-pass"; if (["启用中", "运营中"].includes(String(status || ""))) return "test-starting"; if (["待生效", "待配置"].includes(String(status || ""))) return "test-take-effect"; if (["草稿", "已停用"].includes(String(status || ""))) return "test-stop"; src/views/testDemo/operationWorkbench/workbench.constants.ts
@@ -74,6 +74,10 @@ // 三类对象在左侧筛选区共用的状态筛选项。 export const statusOptionsMap: Record<WorkbenchTab, OptionItem[]> = { fence: [ { label: "待测试", value: "wait-test" }, { label: "测试中", value: "testing" }, { label: "测试不通过", value: "test-fail" }, { label: "可运营", value: "operable" }, { label: "草稿", value: "draft" }, { label: "待生效", value: "pending" }, { label: "启用中", value: "enabled" }, src/views/testDemo/operationWorkbench/workbench.fence-workflow.ts
@@ -15,7 +15,7 @@ id: "", electronicName: "", areaDescription: "", testStatus: 1, flowStatus: 1, areaType: 1, areaStatus: 1, effectType: 2, @@ -119,7 +119,7 @@ id: String(detail?.id || ""), electronicName: detail?.electronicName || "", areaDescription: operationArea?.areaDescription || "", testStatus: Number(detail?.testStatus) || 1, flowStatus: Number(detail?.flowStatus) || 1, areaType: 1, areaStatus: Number(operationArea?.areaStatus) || 1, effectType: detail ? Number(detail?.effectType) || 2 : 2, src/views/testDemo/operationWorkbench/workbench.payload.ts
@@ -181,6 +181,7 @@ fenceCreateMode: boolean; fenceId?: string; currentStatus?: number; hasGeometryChanged?: boolean; cityValue: string; fenceDetailCityCode?: string | number; saveType: number; @@ -198,6 +199,7 @@ fenceCreateMode, fenceId, currentStatus, hasGeometryChanged, cityValue, fenceDetailCityCode, saveType, @@ -238,6 +240,15 @@ return { ...(fenceCreateMode ? {} : { id: fenceId }), status, flowStatus: // 围栏运营区或禁行区边界一旦变更,保存后需要回到待测试状态。 hasGeometryChanged ? 1 : Number(fenceForm.flowStatus) === 3 ? 5 : [1, 2, 3, 4, 5].includes(Number(fenceForm.flowStatus)) ? Number(fenceForm.flowStatus) : 1, cityCode: fenceCreateMode ? cityValue || fenceDetailCityCode || 0 : fenceDetailCityCode || cityValue || 0,