董国庆
2026-06-22 287abeed7edd015a68fe8dbaa79724cb7489a025
修改
7个文件已修改
229 ■■■■■ 已修改文件
src/components/operationWorkbench/PointNameConflictDialog.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/point.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/testOperationWorkbench.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/list.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/map.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/detail.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/operationWorkbench/PointNameConflictDialog.vue
@@ -17,9 +17,12 @@
  pointNumber?: string;
} | null;
type PointIdentity = number | string | null | undefined;
const props = defineProps<{
  validateNameConflict?: (
    name: string
    name: string,
    pointId?: number | string
  ) => Promise<DuplicateNameCheckResult> | DuplicateNameCheckResult;
}>();
@@ -31,6 +34,8 @@
const legacyPointName = ref("");
const currentPointNameError = ref("");
const legacyPointNameError = ref("");
const currentPointId = ref<PointIdentity>(undefined);
const legacyPointId = ref<PointIdentity>(undefined);
let dialogSettled = false;
let dialogResolver:
@@ -45,6 +50,8 @@
  legacyPointName.value = "";
  currentPointNameError.value = "";
  legacyPointNameError.value = "";
  currentPointId.value = undefined;
  legacyPointId.value = undefined;
}
function finalizeDialog(result: PointNameConflictDialogResult | null) {
@@ -88,7 +95,11 @@
    clearFieldError(field);
    return false;
  }
  const duplicatePoint = await props.validateNameConflict(normalizedName);
  const pointId = field === "current" ? currentPointId.value : legacyPointId.value;
  const duplicatePoint = await props.validateNameConflict(
    normalizedName,
    pointId ?? undefined
  );
  if (!duplicatePoint) {
    clearFieldError(field);
    return false;
@@ -164,12 +175,18 @@
  if (action === "rename-legacy-point") legacyPointName.value = "";
});
function open(options?: { duplicateName?: string }) {
function open(options?: {
  duplicateName?: string;
  currentPointId?: PointIdentity;
  legacyPointId?: PointIdentity;
}) {
  dialogSettled = false;
  duplicateName.value = String(options?.duplicateName || "").trim();
  selectedAction.value = "rename-current-point";
  currentPointName.value = "";
  legacyPointName.value = "";
  currentPointId.value = options?.currentPointId;
  legacyPointId.value = options?.legacyPointId;
  visible.value = true;
  return new Promise<PointNameConflictDialogResult | null>((resolve) => {
    dialogResolver = resolve;
src/service/api/point.ts
@@ -7,9 +7,12 @@
  return request.Post<Service.ResponseResult<Entity.Point[]>>('/map/api/v1/point/list', params)
}
export function checkPointNameDuplicate(pointName: string) {
export function checkPointNameDuplicate(params: {
  pointName: string
  id?: number | string
}) {
  return request.Get<Service.ResponseResult<any>>('/map/api/v1/point/checkNameDuplicate', {
    params: { pointName },
    params,
  })
}
src/service/api/testOperationWorkbench.ts
@@ -49,9 +49,12 @@
  return request.Post<Service.ResponseResult<Entity.Point[]>>('/map/api/v1/point/listDemo', params)
}
export function checkPointNameDuplicate(pointName: string) {
export function checkPointNameDuplicate(params: {
  pointName: string
  id?: number | string
}) {
  return request.Get<Service.ResponseResult<any>>('/map/api/v1/point/checkNameDuplicate', {
    params: { pointName },
    params,
  })
}
src/views/businessOperations/operationWorkbench/map.vue
@@ -5385,6 +5385,8 @@
interface PointNameConflictDialogExposed {
  open: (options?: {
    duplicateName?: string
    currentPointId?: number | string
    legacyPointId?: number | string
  }) => Promise<PointNameConflictDialogResult | null>
}
@@ -5414,12 +5416,19 @@
  }
}
async function findDuplicatePointByName(pointName: string) {
async function findDuplicatePointByName(
  pointName: string,
  id?: number | string,
) {
  const normalizedName = String(pointName || '').trim()
  if (!normalizedName)
    return null
  const normalizedId = String(id || '').trim()
  try {
    const res = await checkPointNameDuplicate(normalizedName)
    const res = await checkPointNameDuplicate({
      pointName: normalizedName,
      id: normalizedId || undefined,
    })
    if (res.code !== 200)
      return null
    return normalizeDuplicatePointInfo(res.data, normalizedName)
@@ -5460,7 +5469,11 @@
async function preparePointSavePayloadForConflict<T extends Record<string, any>>(
  payload: T,
) {
  const duplicatePoint = await findDuplicatePointByName(payload.pointName || '')
  const currentPointId = payload.id
  const duplicatePoint = await findDuplicatePointByName(
    payload.pointName || '',
    currentPointId,
  )
  if (!duplicatePoint) {
    return {
      shouldContinue: true,
@@ -5470,6 +5483,8 @@
  const resolvePayload = await pointNameConflictDialogRef.value?.open({
    duplicateName: duplicatePoint.pointName,
    currentPointId,
    legacyPointId: duplicatePoint.id,
  })
  if (!resolvePayload) {
    return {
src/views/testDemo/operationWorkbench/list.vue
@@ -2,7 +2,7 @@
import noDataImg from '@/assets/element-img/no-data.png'
import downIcon from '@/assets/element-img/down.png'
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'
@@ -95,6 +95,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 },
@@ -117,6 +124,11 @@
  electronicNumberOrName: '',
  electronicAreaId: null as string | null,
  statusList: null as number[] | null,
  type: null as number | null,
  typeStr: null as number[] | null,
  lifecycleRange: null as [number, number] | null,
  startTime: '',
  endTime: '',
  updateBy: '',
  updateTime: null as [number, number] | null,
  createBy: '',
@@ -202,6 +214,8 @@
const isPointAreaSelectOpen = ref(false)
const isFenceStatusListOpen = ref(false)
const isFenceElectronicAreaIdOpen = ref(false)
const isFenceStrategyTypeOpen = ref(false)
const isFenceStrategyWeekOpen = ref(false)
const isStationStatusOpen = ref(false)
const isStationPointNumOpen = ref(false)
const isFenceCityOpen = ref(false)
@@ -328,6 +342,28 @@
  return fenceStrategyDateTypeOptions.find(option => option.value === Number(item?.type))?.label || '-'
}
function normalizeFenceStrategyWeekFilter(value: Array<number | string> | null | undefined) {
  const normalized = (Array.isArray(value) ? value : [])
    .map(item => Number(item))
    .filter(item => Number.isFinite(item))
  if (normalized.includes(0))
    return [0]
  return Array.from(new Set(normalized)).sort((a, b) => a - b)
}
function handleFenceStrategyTypeFilterChange(value: number | null) {
  fenceModel.value.type = value
  if (value !== 3)
    fenceModel.value.typeStr = null
}
function handleFenceStrategyWeekFilterChange(value: Array<number | string> | null) {
  const normalized = normalizeFenceStrategyWeekFilter(value)
  fenceModel.value.typeStr = normalized.length ? normalized : null
}
function formatFenceStrategyTimeList(item: Record<string, any>) {
  const times = Array.isArray(item?.time) ? item.time : []
  return times
@@ -422,6 +458,18 @@
function getDateRangeEnd(value: [number, number] | null | undefined) {
  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() {
@@ -632,6 +680,12 @@
    pageSize: pagination.pageSize,
    electronicNumberOrName: fenceModel.value.electronicNumberOrName,
    statusList: fenceModel.value.statusList,
    type: fenceModel.value.type,
    typeStr: fenceModel.value.type === 3 ? fenceModel.value.typeStr : null,
    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,
@@ -1573,6 +1627,94 @@
        ),
      },
      {
        label: '运营策略',
        path: 'type',
        render: () => (
          <NSelect
            value={fenceModel.value.type}
            options={fenceStrategyTypeOptions}
            placeholder="请选择"
            clearable
            onUpdate:value={handleFenceStrategyTypeFilterChange}
            onUpdate:show={(val: boolean) => isFenceStrategyTypeOpen.value = val}
          >
            {{
              arrow: () => (
                <img
                  src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
                  class={`arrow-icon ${isFenceStrategyTypeOpen.value ? 'arrow-icon--open' : ''}`}
                  style={{ width: '16px', height: '16px' }}
                  alt="arrow"
                />
              ),
            }}
          </NSelect>
        ),
      },
      {
        label: '生命周期',
        path: 'lifecycleRange',
        render: () => (
          <NDatePicker v-model:value={fenceModel.value.lifecycleRange} type="daterange" clearable actions={[]} close-on-select={true} format="yyyy/MM/dd" />
        ),
      },
      ...(fenceModel.value.type === 3
        ? [{
            label: '自定义周',
            path: 'typeStr',
            render: () => (
              <NSelect
                value={fenceModel.value.typeStr}
                options={fenceStrategyWeekOptions}
                placeholder="请选择自定义周"
                clearable
                multiple
                max-tag-count={1}
                onUpdate:show={(val: boolean) => isFenceStrategyWeekOpen.value = val}
                onUpdate:value={handleFenceStrategyWeekFilterChange}
              >
                {{
                  arrow: () => (
                    <img
                      src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
                      class={`arrow-icon ${isFenceStrategyWeekOpen.value ? 'arrow-icon--open' : ''}`}
                      style={{ width: '16px', height: '16px' }}
                      alt="arrow"
                    />
                  ),
                }}
              </NSelect>
            ),
          }]
        : []),
      {
        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>
        ),
      },
      {
        label: '更新人',
        path: 'updateBy',
        render: () => <NInput v-model:value={fenceModel.value.updateBy} />,
src/views/testDemo/operationWorkbench/map.vue
@@ -6979,6 +6979,8 @@
interface PointNameConflictDialogExposed {
  open: (options?: {
    duplicateName?: string
    currentPointId?: number | string
    legacyPointId?: number | string
  }) => Promise<PointNameConflictDialogResult | null>
}
@@ -7008,12 +7010,19 @@
  }
}
async function findDuplicatePointByName(pointName: string) {
async function findDuplicatePointByName(
  pointName: string,
  id?: number | string,
) {
  const normalizedName = String(pointName || '').trim()
  if (!normalizedName)
    return null
  const normalizedId = String(id || '').trim()
  try {
    const res = await checkPointNameDuplicate(normalizedName)
    const res = await checkPointNameDuplicate({
      pointName: normalizedName,
      id: normalizedId || undefined,
    })
    if (res.code !== 200)
      return null
    return normalizeDuplicatePointInfo(res.data, normalizedName)
@@ -7054,7 +7063,11 @@
async function preparePointSavePayloadForConflict<T extends Record<string, any>>(
  payload: T,
) {
  const duplicatePoint = await findDuplicatePointByName(payload.pointName || '')
  const currentPointId = payload.id
  const duplicatePoint = await findDuplicatePointByName(
    payload.pointName || '',
    currentPointId,
  )
  if (!duplicatePoint) {
    return {
      shouldContinue: true,
@@ -7064,6 +7077,8 @@
  const resolvePayload = await pointNameConflictDialogRef.value?.open({
    duplicateName: duplicatePoint.pointName,
    currentPointId,
    legacyPointId: duplicatePoint.id,
  })
  if (!resolvePayload) {
    return {
src/views/testDemo/testPlanManagement/detail.vue
@@ -178,7 +178,7 @@
const filterItems = computed(() => [
  {
    label: '订单号',
    label: '订单编号',
    path: 'orderNo',
    render: () => <n-input v-model:value={searchModel.value.orderNo} placeholder="请输入" clearable allow-input={onlyAllowNumberAndLetter} />,
  },
@@ -219,7 +219,7 @@
    ),
  },
  {
    label: '车辆',
    label: '接单车辆',
    path: 'plateNumber',
    render: () => (
      <n-select
@@ -237,7 +237,7 @@
    ),
  },
  {
    label: '订单状态',
    label: '状态',
    path: 'status',
    render: () => (
      <n-select