董国庆
2026-05-07 6fbc3ef5fe476892471b2148fc861398a6a8e717
fix(operationWorkbench): 修复列表刷新、状态筛选及详情加载问题

- 修复场站列表点位数量和状态筛选参数传递错误
- 修复从地图视图返回列表视图时筛选条件重置问题
- 修复点位和场站详情加载逻辑,确保编辑模式正确进入
- 添加列表刷新标记,确保数据变更后列表及时更新
- 优化点位编辑保存后的离开确认流程
7个文件已修改
265 ■■■■■ 已修改文件
src/service/api/point.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/workbench.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/components/FencePanel.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/list.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.scss 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/status.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/point.ts
@@ -19,7 +19,7 @@
  return request.Post<Service.ResponseResult<any>>('/map/api/v1/point/enableDisable', data)
}
export function fetchPointDetail(id: number) {
export function fetchPointDetail(id: number|string) {
  return request.Get<Service.ResponseResult<Entity.Point>>('/map/api/v1/point/queryById', { params: { id } })
}
src/store/workbench.ts
@@ -9,6 +9,7 @@
    status: string
  }
  version: number
  listNeedRefresh: boolean
}
const DEFAULT_WORKBENCH_CONTEXT: WorkbenchState['context'] = {
@@ -24,11 +25,13 @@
    return {
      context: { ...DEFAULT_WORKBENCH_CONTEXT },
      version: 0,
      listNeedRefresh: false,
    }
  },
  actions: {
    resetContext() {
      this.context = { ...DEFAULT_WORKBENCH_CONTEXT }
      this.listNeedRefresh = false
      this.version += 1
    },
    clearEntryIntent() {
@@ -61,5 +64,13 @@
      }
      this.version += 1
    },
    markListNeedRefresh() {
      this.listNeedRefresh = true
      this.version += 1
    },
    clearListRefreshFlag() {
      this.listNeedRefresh = false
      this.version += 1
    },
  },
})
src/views/businessOperations/operationWorkbench/components/FencePanel.vue
@@ -1148,7 +1148,7 @@
  padding-left: 11px;
  color: #171f26;
  font-size: 16px;
  font-weight: 500;
  font-weight: 600;
  line-height: 24px;
  &::before {
src/views/businessOperations/operationWorkbench/list.vue
@@ -119,8 +119,8 @@
const stationInitialModel = {
  cityCode: DEFAULT_CITY_CODE,
  stationNameOrNumber: '',
  pointCountRange: null as string | null,
  status: null as number | null,
  stationPointNum: null as string[] | null,
  statusList: null as number[] | null,
  updateTime: null as [number, number] | null,
  createBy: '',
  createTime: null as [number, number] | null,
@@ -592,23 +592,10 @@
const showCityColumn = computed(() => String(getCurrentTabModel().cityCode || '') === ALL_CITY_VALUE)
function getStationPointCountParams(range: string | null) {
  if (!range)
    return {}
  if (range === 'gt5') {
    return {
      pointCountMin: 6,
      pointCountMax: undefined,
    }
  }
  const count = Number(range)
  if (!Number.isNaN(count)) {
    return {
      pointCountMin: count,
      pointCountMax: count,
    }
  }
  return {}
function buildStationPointNumParam(values: string[] | null) {
  if (!values?.length)
    return undefined
  return values.join(',')
}
function syncWorkbenchContext() {
@@ -715,14 +702,13 @@
}
async function loadStationList() {
  const pointCountParams = getStationPointCountParams(stationModel.value.pointCountRange)
  const params = {
    pageNum: pagination.page,
    pageSize: pagination.pageSize,
    cityCode: buildQueryCityCode(stationModel.value.cityCode),
    stationNameOrNumber: stationModel.value.stationNameOrNumber || undefined,
    ...pointCountParams,
    status: stationModel.value.status ?? undefined,
    stationPointNum: buildStationPointNumParam(stationModel.value.stationPointNum),
    statusList: stationModel.value.statusList ?? undefined,
    updateStartTime: getDateRangeStart(stationModel.value.updateTime),
    updateEndTime: getDateRangeEnd(stationModel.value.updateTime),
    createBy: stationModel.value.createBy || undefined,
@@ -762,14 +748,15 @@
}
function resetCurrentSearch() {
  const currentCityCode = String(getCurrentTabModel().cityCode || cityValue.value || DEFAULT_CITY_CODE)
  if (activeTab.value === 'fence') {
    fenceModel.value = { ...fenceInitialModel }
    fenceModel.value = { ...fenceInitialModel, cityCode: currentCityCode }
  }
  else if (activeTab.value === 'station') {
    stationModel.value = { ...stationInitialModel }
    stationModel.value = { ...stationInitialModel, cityCode: currentCityCode }
  }
  else {
    pointModel.value = { ...pointInitialModel }
    pointModel.value = { ...pointInitialModel, cityCode: currentCityCode }
  }
  syncTabCityFromModel()
  pagination.page = 1
@@ -1570,25 +1557,27 @@
      },
      {
        label: '点位数量',
        path: 'pointCountRange',
        path: 'stationPointNum',
        render: () => (
          <NSelect
            v-model:value={stationModel.value.pointCountRange}
            v-model:value={stationModel.value.stationPointNum}
            options={stationPointCountOptions}
            placeholder="请选择"
            clearable
            multiple
          />
        ),
      },
      {
        label: '场站状态',
        path: 'status',
        path: 'statusList',
        render: () => (
          <NSelect
            v-model:value={stationModel.value.status}
            v-model:value={stationModel.value.statusList}
            options={stationStatusOptions}
            placeholder="请选择"
            clearable
            multiple
            onUpdate:show={(val: boolean) => isStationStatusOpen.value = val}
          >
            {{
@@ -2284,6 +2273,7 @@
  (newTab, oldTab) => {
    if (!initialized.value || !oldTab)
      return
    checkedRowKeys.value = []
    if (isApplyingWorkbenchContext.value) {
      syncTabCityFromModel()
      return
@@ -2310,6 +2300,12 @@
    ? workbenchStore.context.tab as WorkbenchTab
    : activeTab.value
  const nextCityCode = workbenchStore.context.cityCode || cityValue.value
  const legacyNeedRefresh = window.sessionStorage.getItem('geofenceManagementNeedRefresh') === '1'
  const needRefresh = workbenchStore.listNeedRefresh || legacyNeedRefresh
  if (legacyNeedRefresh)
    window.sessionStorage.removeItem('geofenceManagementNeedRefresh')
  if (workbenchStore.listNeedRefresh)
    workbenchStore.clearListRefreshFlag()
  if (nextTab !== activeTab.value || nextCityCode !== cityValue.value) {
    isApplyingWorkbenchContext.value = true
    if (nextTab === 'fence')
@@ -2323,11 +2319,8 @@
    isApplyingWorkbenchContext.value = false
    return
  }
  if (window.sessionStorage.getItem('geofenceManagementNeedRefresh') === '1') {
    window.sessionStorage.removeItem('geofenceManagementNeedRefresh')
    if (activeTab.value === 'fence')
      getList()
  }
  if (needRefresh)
    getList()
})
onBeforeRouteLeave((to) => {
@@ -2409,9 +2402,7 @@
          </n-flex>
          <n-flex align="center">
            <NButton @click="handleBackToMap">
              查看地图视图
            </NButton>
            <NButton @click="handleBackToMap"> 查看地图视图 </NButton>
            <NButton
              v-if="activeTab === 'fence'"
              type="primary"
@@ -2588,12 +2579,14 @@
}
:deep(.strategy-label) {
  flex: 1;
  flex-shrink: 0;
  color: rgba(23, 31, 38, .6);
  color: rgba(23, 31, 38, 0.6);
}
:deep(.strategy-time) {
  margin-left: 4px;
  flex: 1;
  word-break: break-all;
  color: rgba(23, 31, 38, 1);
}
src/views/businessOperations/operationWorkbench/map.scss
@@ -486,6 +486,7 @@
  font-size: 12px;
  line-height: 16px;
  text-align: center;
  border-radius: 4px;
}
.wait-test {
src/views/businessOperations/operationWorkbench/map.vue
@@ -16,6 +16,7 @@
  createPoint,
  deletePoint,
  fetchElectronicFenceAreaList,
  fetchPointDetail,
  fetchPointList,
  updatePoint,
} from "@/service/api/point";
@@ -424,6 +425,7 @@
const pointAddressQuery = ref("");
const isSearchingPointAddress = ref(false);
const isPointAddressDropdownVisible = ref(false);
const pointDetailLoading = ref(false);
const fenceDetailLoading = ref(false);
const fenceSaving = ref(false);
const fenceEditMode = ref(false);
@@ -574,10 +576,13 @@
    );
  if (activeTab.value === "station" && stationCreateMode.value) return null;
  if (stationPanelClosed.value) return null;
  const matchedStation = tabDataMap.value[activeTab.value].find(
    (item) => item.id === selectedId.value
  );
  if (matchedStation) return matchedStation;
  if (String(stationDetailData.value?.id || "") === String(selectedId.value || ""))
    return mapStationApiItemToListItem(stationDetailData.value);
  return (
    tabDataMap.value[activeTab.value].find(
      (item) => item.id === selectedId.value
    ) ||
    currentList.value[0] ||
    null
  );
@@ -2807,6 +2812,7 @@
  const workflowState = buildStationEditModeState(detail);
  stationCreateMode.value = workflowState.stationCreateMode;
  stationEditMode.value = workflowState.stationEditMode;
  stationPanelClosed.value = false;
  stationForm.value = workflowState.form;
  selectedStationPointId.value = workflowState.selectedStationPointId;
  enterStationDraftByDetail(detail);
@@ -2987,10 +2993,12 @@
      }
    }
    if (isCreatingStation) {
      workbenchStore.markListNeedRefresh();
      closeStationDetailPanelAfterSave(refreshedStation?.id || detail.id);
      window.$message?.success(saveType === 0 ? "保存草稿成功" : "添加成功");
      return;
    }
    workbenchStore.markListNeedRefresh();
    closeStationDetailPanelAfterSave(refreshedStation?.id || detail.id);
    window.$message?.success(
      saveType === 0 ? "保存草稿成功" : isCreatingStation ? "添加成功" : "编辑成功"
@@ -3058,6 +3066,14 @@
    (stationCreateMode.value || pendingWorkbenchType.value === "create")
  )
    return;
  const selectedStationDetailId = String(stationDetailData.value?.id || "");
  if (
    activeTab.value === "station" &&
    selectedId.value &&
    selectedStationDetailId &&
    selectedStationDetailId === selectedId.value
  )
    return;
  if (!list.length) {
    selectedId.value = "";
    return;
@@ -3102,7 +3118,7 @@
      pointCreateReady.value = false;
      pointEditSnapshot.value = "";
      cleanupPointEditMapState();
      syncPointDetailFromSelected();
      await loadPointDetailData(id);
      if (pendingWorkbenchType.value === "edit") {
        handleEnterPointEdit();
      }
@@ -3235,9 +3251,16 @@
          : await loadStationDetailData(entryContext.id);
        if (entryContext.type === "edit" && stationDetail)
          applyStationEditByDetail(stationDetail);
        await nextTick();
        focusSelectedEntity(true);
      } else if (entryContext.tab === "point") {
        syncPointDetailFromSelected();
        if (entryContext.type === "edit") handleEnterPointEdit();
        const hasLoadedPointDetail =
          String(pointDetailData.value?.id || "") === entryContext.id;
        const pointDetail = hasLoadedPointDetail
          ? pointDetailData.value
          : await loadPointDetailData(entryContext.id);
        if (entryContext.type === "edit" && pointDetail)
          handleEnterPointEdit();
      }
      pendingWorkbenchType.value = "";
      consumeWorkbenchEntryQuery(routeQuery);
@@ -3607,6 +3630,20 @@
  });
}
function openPointSaveLeaveDialog() {
  return new Promise<"save" | "discard" | "cancel">((resolve) => {
    window.$dialog?.warning({
      title: "当前点位还未保存,确定离开吗?",
      content: "删除点位,编辑的内容将丢失,确定离开吗?",
      positiveText: "保存离开",
      negativeText: "丢弃离开",
      onPositiveClick: () => resolve("save"),
      onNegativeClick: () => resolve("discard"),
      onClose: () => resolve("cancel"),
    });
  });
}
async function confirmFenceOperationStrategyShortenEdit(
  id: number | string,
  operationStrategy: string
@@ -3637,30 +3674,18 @@
}
async function confirmExitPointCreateTool() {
  if (!hasUnsavedPointEdit()) return true;
  const keepData = await openConfirmDialog(
    "当前点位存在未保存内容,是否保留?",
    "保留后将继续当前打点;不保留将删除已新建的数据并关闭。",
    {
      positiveText: "保留",
      negativeText: "不保留",
    }
  );
  return !keepData;
  return await confirmDiscardPointEdit();
}
async function confirmDiscardPointEdit() {
  if (!hasUnsavedPointEdit()) return true;
  if (pointCreateMode.value) {
    const keepEditing = await openConfirmDialog(
      "当前点位还未保存,确定离开吗?",
      "删除点位,编辑的内容将丢失,确定离开吗?",
      {
        positiveText: "保存离开",
        negativeText: "丢弃离开",
      }
    );
    return !keepEditing;
    const action = await openPointSaveLeaveDialog();
    if (action === "save")
      return await handleSavePointEdit();
    if (action === "discard")
      return true;
    return false;
  }
  return await openConfirmDialog(
    "确定放弃本次修改吗?",
@@ -4174,17 +4199,7 @@
async function handleDeletePointEditMarker() {
  if (pointCreateMode.value) {
    if (hasUnsavedPointEdit()) {
      const keepEditing = await openConfirmDialog(
        "当前点位还未保存,确定离开吗?",
        "删除点位,编辑的内容将丢失,确定离开吗?",
        {
          positiveText: "继续编辑",
          negativeText: "丢弃离开",
        }
      );
      if (keepEditing) return;
    }
    if (!(await confirmDiscardPointEdit())) return;
    exitPointCreateMode();
    pointPanelClosed.value = true;
    return;
@@ -4637,11 +4652,46 @@
  return null;
}
async function loadPointDetailData(id: string) {
  pointDetailLoading.value = true;
  pointEditMode.value = false;
  pointCreateMode.value = false;
  pointCreateReady.value = false;
  try {
    const numericId = String(id);
    const res = await fetchPointDetail(numericId);
    if (res.code === 200 && res.data) {
      pointDetailData.value = res.data;
      resetPointForm(res.data);
      return res.data;
    }
    pointDetailData.value = null;
  } catch (error) {
    console.error("加载点位详情失败:", error);
    const fallbackDetail = (selectedItem.value?.raw || null) as Entity.Point | null;
    if (fallbackDetail) {
      pointDetailData.value = fallbackDetail;
      resetPointForm(fallbackDetail);
      return fallbackDetail;
    }
    pointDetailData.value = null;
  } finally {
    pointDetailLoading.value = false;
  }
  return null;
}
// 进入点位编辑时先重新渲染地图,再创建临时点位,避免和原始 marker 重叠。
function handleEnterPointEdit() {
  if (!pointDetailData.value) return;
async function handleEnterPointEdit() {
  if (activeTab.value !== "point") return;
  const targetId = String(selectedId.value || selectedItem.value?.id || "");
  if (!targetId) return;
  const detail = String(pointDetailData.value?.id || "") === targetId
    ? pointDetailData.value
    : await loadPointDetailData(targetId);
  if (!detail) return;
  const workflowState = buildPointEditModeState({
    detail: pointDetailData.value,
    detail,
    fallbackPointPictures: selectedItem.value?.raw?.pointPictures,
    defaultPointImage: DEFAULT_POINT_IMAGE,
  });
@@ -5044,31 +5094,31 @@
}
async function handleSavePointEdit() {
  if (!isPointFormMode.value) return;
  if (!isPointFormMode.value) return false;
  if (!pointForm.value.pointName.trim()) {
    window.$message?.error("请输入点位名称");
    return;
    return false;
  }
  if (!pointForm.value.pointAddress.trim()) {
    window.$message?.error("请输入点位地址");
    return;
    return false;
  }
  if (!pointForm.value.pointOnOff) {
    window.$message?.error("请选择点位属性");
    return;
    return false;
  }
  if (pointForm.value.pointLon === null || pointForm.value.pointLat === null) {
    window.$message?.error("请先通过地图选点或地址搜索确定点位坐标");
    return;
    return false;
  }
  if (pointForm.value.parkingTimeLimit) {
    if (!/^\d+$/.test(pointForm.value.parkingTimeLimit)) {
      window.$message?.error("限停时长需为数字");
      return;
      return false;
    }
    if (Number(pointForm.value.parkingTimeLimit) > 60) {
      window.$message?.error("限停时长最大为60分钟");
      return;
      return false;
    }
  }
  const supportOperationTime = ["可运营", "待生效", "启用中"].includes(
@@ -5076,14 +5126,14 @@
  );
  if (supportOperationTime && !pointForm.value.parkingTimeLimit) {
    window.$message?.error("请输入限停时长");
    return;
    return false;
  }
  if (
    supportOperationTime &&
    pointForm.value.operationType === "2" &&
    !validatePointTimeRanges()
  ) {
    return;
    return false;
  }
  if (
    Number(
@@ -5094,11 +5144,11 @@
    !pointForm.value.remarks.trim()
  ) {
    window.$message?.error("请填写测试不通过原因");
    return;
    return false;
  }
  if (uploadedImages.value.some((img) => img.status === "uploading")) {
    window.$message?.warning("图片上传中,请等待上传完成后再保存");
    return;
    return false;
  }
  const operationArea = resolveOperationArea(
    fenceAreaData.value,
@@ -5154,6 +5204,7 @@
          pointNumber: pointDetailData.value!.pointNumber,
        });
    if (res.code === 200) {
      workbenchStore.markListNeedRefresh();
      window.$message?.success("保存成功");
      const createdPointNumber = isCreatingPoint
        ? String(res?.data?.pointNumber || (res as any)?.pointNumber || "")
@@ -5172,16 +5223,19 @@
        if (createdItem) {
          pointPanelClosed.value = false;
          selectedId.value = createdItem.id;
          syncPointDetailFromSelected();
          return;
          await loadPointDetailData(String(createdItem.id));
          return true;
        }
      }
      syncPointDetailFromSelected();
      return;
      if (selectedId.value)
        await loadPointDetailData(String(selectedId.value));
      return true;
    }
    return false;
    // window.$message?.error(res.msg || "保存失败");
  } catch (error) {
    console.error("保存点位失败:", error);
    return false;
    // window.$message?.error("保存失败");
  } finally {
    pointSaving.value = false;
@@ -5305,6 +5359,7 @@
      ? await addFence(params)
      : await editFence(params);
    if (res.code === 200) {
      workbenchStore.markListNeedRefresh();
      window.$message?.success(
        saveType === 0
          ? "保存草稿成功"
@@ -5655,6 +5710,21 @@
    });
    map.value.on("moveend", handlePointMapMoveEnd);
    await loadWorkbenchData();
    if (isPointFormMode.value && pointEditPosition.value) {
      createOrUpdatePointEditMarker(pointEditPosition.value);
      map.value.setCenter(pointEditPosition.value);
    }
    if (
      activeTab.value === "station" &&
      String(route.query.type || "") === "edit" &&
      selectedId.value
    ) {
      const detail = String(stationDetailData.value?.id || "") === String(selectedId.value)
        ? stationDetailData.value
        : await loadStationDetailData(String(selectedId.value));
      if (detail)
        applyStationEditByDetail(detail);
    }
    if (isFenceFormMode.value && fenceDetailData.value) {
      enterFenceDraftByDetail(fenceDetailData.value);
      renderMapEntities();
src/views/businessOperations/operationWorkbench/status.ts
@@ -97,7 +97,7 @@
  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";
  if (["草稿", "已停用"].includes(String(status || ""))) return "test-stop";
  if (["已失效"].includes(String(status || ""))) return "test-invalid";
  return "";
}