董国庆
2026-06-01 61746b771d320d60e021fd239379715a189c803f
feat(operationWorkbench): 新增场站停用确认弹窗及围栏搜索筛选条件

- 新增编辑场站切换为停用状态时的点位批量确认逻辑,弹出提示询问用户是否批量停用所有点位
- 为电子围栏列表添加运营策略、生命周期范围、运营时间的搜索筛选项
- 修正批量导出按钮文案,区分点位与场站导出
3个文件已修改
130 ■■■■■ 已修改文件
src/views/businessOperations/operationWorkbench/list.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/workbench.payload.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/list.vue
@@ -1,6 +1,6 @@
<script setup lang="tsx">
import type { DataTableColumns, FormInst } from 'naive-ui'
import { NButton, NDataTable, NDatePicker, NInput, NSelect, NSpace, NTag, NTooltip } from 'naive-ui'
import { NButton, NDataTable, NDatePicker, NInput, NSelect, NSpace, NTag, NTimePicker, NTooltip } from 'naive-ui'
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
import dayjs from 'dayjs'
@@ -78,6 +78,13 @@
  { label: '20以上禁行区域', value: '20' },
]
const fenceStrategyTypeOptions = [
  { label: '工作日', value: 0 },
  { label: '法定节假日', value: 1 },
  { label: '非工作日', value: 2 },
  { label: '自定义时间', value: 3 },
]
const stationStatusOptions = [
  { label: '草稿', value: 0 },
  { label: '待生效', value: 1 },
@@ -100,6 +107,10 @@
  electronicNumberOrName: '',
  electronicAreaId: null as string | null,
  statusList: null as number[] | null,
  type: null as number | null,
  lifecycleRange: null as [number, number] | null,
  startTime: '',
  endTime: '',
  updateBy: '',
  updateTime: null as [number, number] | null,
  createBy: '',
@@ -412,6 +423,18 @@
  return value ? dayjs(value[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss') : undefined
}
function parseTimePickerValue(value?: string | null) {
  const normalizedValue = String(value || '').trim()
  if (!normalizedValue)
    return null
  const parsed = dayjs(`2000-01-01 ${normalizedValue}`)
  return parsed.isValid() ? parsed.valueOf() : null
}
function formatTimePickerValue(value: number | null) {
  return typeof value === 'number' ? dayjs(value).format('HH:mm') : ''
}
function getCurrentTabModel() {
  if (activeTab.value === 'fence')
    return fenceModel.value
@@ -623,6 +646,11 @@
    pageSize: pagination.pageSize,
    electronicNumberOrName: fenceModel.value.electronicNumberOrName,
    statusList: fenceModel.value.statusList,
    type: fenceModel.value.type,
    effectTime: fenceModel.value.lifecycleRange ? dayjs(fenceModel.value.lifecycleRange[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss') : null,
    failureTime: fenceModel.value.lifecycleRange ? dayjs(fenceModel.value.lifecycleRange[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss') : null,
    startTime: fenceModel.value.startTime || null,
    endTime: fenceModel.value.endTime || null,
    createBy: fenceModel.value.createBy,
    updateBy: fenceModel.value.updateBy,
    updateStartTime: fenceModel.value.updateTime ? dayjs(fenceModel.value.updateTime[0]).format('YYYY-MM-DD HH:mm:ss') : null,
@@ -1480,6 +1508,52 @@
              ),
            }}
          </NSelect>
        ),
      },
      {
        label: '运营策略',
        path: 'type',
        render: () => (
          <NSelect
            v-model:value={fenceModel.value.type}
            options={fenceStrategyTypeOptions}
            placeholder="请选择"
            clearable
          />
        ),
      },
      {
        label: '生命周期',
        path: 'lifecycleRange',
        render: () => (
          <NDatePicker v-model:value={fenceModel.value.lifecycleRange} type="daterange" clearable actions={[]} close-on-select={true} format="yyyy/MM/dd" />
        ),
      },
      {
        label: '运营时间',
        path: 'operationTime',
        render: () => (
          <div style={{ display: 'flex', alignItems: 'center', gap: '8px', width: '100%' }}>
            <NTimePicker
              value={parseTimePickerValue(fenceModel.value.startTime)}
              format="HH:mm"
              placeholder="开始时间"
              clearable
              actions={['now']}
              style={{ width: 'calc(50% - 12px)' }}
              onUpdate:value={value => fenceModel.value.startTime = formatTimePickerValue(value)}
            />
            <span>至</span>
            <NTimePicker
              value={parseTimePickerValue(fenceModel.value.endTime)}
              format="HH:mm"
              placeholder="结束时间"
              clearable
              actions={['now']}
              style={{ width: 'calc(50% - 12px)' }}
              onUpdate:value={value => fenceModel.value.endTime = formatTimePickerValue(value)}
            />
          </div>
        ),
      },
      {
@@ -2399,7 +2473,7 @@
                :disabled="checkedRowKeys.length === 0"
                @click="handlePointBatchExport"
              >
                批量导出
                批量导出点位
              </NButton>
            </template>
            <template v-else-if="activeTab === 'station'">
@@ -2411,7 +2485,7 @@
                :disabled="checkedRowKeys.length === 0"
                @click="handleStationBatchExport"
              >
                批量导出
                批量导出场站
              </NButton>
            </template>
          </n-flex>
src/views/businessOperations/operationWorkbench/map.vue
@@ -2836,6 +2836,35 @@
  return Number(operationArea?.areaStatus || 1);
}
function getStationOriginalAreaStatus() {
  const detail = stationDetailData.value;
  return Number(detail?.areaStatus) === 2 ||
    Number(detail?.status) === 3 ||
    String(detail?.status || detail?.statusText || "").trim() === "已停用"
    ? 2
    : 1;
}
function openStationDisablePointStopDialog(pointCount: number) {
  return new Promise<boolean | null>((resolve) => {
    if (!window.$dialog) {
      resolve(null);
      return;
    }
    window.$dialog.warning({
      title: "提示",
      content: `当前场站包含${pointCount}个点位,确认一起停用?`,
      positiveText: "是",
      negativeText: "否",
      maskClosable: false,
      autoFocus: false,
      onPositiveClick: () => resolve(true),
      onNegativeClick: () => resolve(false),
      onClose: () => resolve(null),
    });
  });
}
async function resolveFencePointRelationPayload() {
  if (fenceCreateMode.value || !fenceDetailData.value)
    return { addPointIdList: [], delPointIdList: [], disablePointIdList: [] };
@@ -3166,9 +3195,24 @@
    return;
  }
  const isCreatingStation = stationCreateMode.value;
  const areaStatus = Number(stationForm.value.areaStatus || 1);
  const pointCount = Array.isArray(stationForm.value.pointList)
    ? stationForm.value.pointList.length
    : 0;
  let pointStop: boolean | undefined;
  if (
    !isCreatingStation &&
    saveType === 1 &&
    areaStatus === 2 &&
    getStationOriginalAreaStatus() !== 2 &&
    pointCount > 0
  ) {
    const confirmResult = await openStationDisablePointStopDialog(pointCount);
    if (confirmResult === null) return;
    pointStop = confirmResult;
  }
  stationSaving.value = true;
  try {
    const areaStatus = Number(stationForm.value.areaStatus || 1);
    const nextStatus =
      saveType === 0 ? "草稿" : areaStatus === 2 ? "已停用" : "启用中";
    const pointList = stationForm.value.pointList.map((item) => ({
@@ -3222,6 +3266,7 @@
      centerLon,
      saveType,
      areaStatus,
      pointStop,
      operationArea,
      polygonPath,
      operationType: stationForm.value.operationType,
src/views/businessOperations/operationWorkbench/workbench.payload.ts
@@ -281,6 +281,7 @@
  centerLon?: number;
  saveType: 0 | 1;
  areaStatus: number;
  pointStop?: boolean;
  operationArea: any;
  polygonPath: LngLatTuple[];
  operationType: "1" | "2";
@@ -298,6 +299,7 @@
    centerLon,
    saveType,
    areaStatus,
    pointStop,
    operationArea,
    polygonPath,
    operationType,
@@ -335,6 +337,7 @@
    fencePoints: stringifyFencePoints(polygonPath) || undefined,
    operationType: Number(operationType || 1),
    parkingTimeLimit: Number(parkingTimeLimit || 0),
    pointStop,
    pointCount: pointList.length,
    remark: description.trim() || undefined,
    stationArea,