| | |
| | | ) |
| | | |
| | | const cityOptions = ref<OptionItem[]>([...DEFAULT_CITY_OPTIONS]) |
| | | const SERVER_REFRESH_DELAY_MS = 1000 |
| | | |
| | | function waitForServerRefreshDelay() { |
| | | return new Promise<void>((resolve) => { |
| | | window.setTimeout(() => resolve(), SERVER_REFRESH_DELAY_MS) |
| | | }) |
| | | } |
| | | |
| | | // 三类实体的列表数据由同一张地图承载,左侧列表只是按当前 tab 切换展示。 |
| | | const pointData = ref<ListItem[]>([]) |
| | |
| | | return String(item.status || '').trim() === '已停用' |
| | | } |
| | | |
| | | // 地图默认态不展示停用/失效场站;即便左侧点中了详情,也不再对地图放行。 |
| | | function isStationHiddenOnMap(item?: ListItem | null) { |
| | | if (!item) |
| | | return true |
| | | const normalizedStatus = normalizeStationDisplayStatus( |
| | | item.raw?.status ?? item.status, |
| | | item.raw?.areaStatus, |
| | | ) |
| | | return ['已停用', '失效', '已失效'].includes(normalizedStatus) |
| | | } |
| | | |
| | | const visibleFenceIdSet = computed(() => { |
| | | return new Set(visibleFenceList.value.map(item => String(item.id))) |
| | | }) |
| | |
| | | pointDetailData.value.verificationStatus, |
| | | ) |
| | | }) |
| | | |
| | | function getPointEditDisplayStatusText(status?: string) { |
| | | const rawStatus = String(status || '').trim() |
| | | if (rawStatus === '待测试' || rawStatus === '测试中') |
| | | return '可运营' |
| | | return rawStatus || '-' |
| | | } |
| | | |
| | | const currentPointDisplayStatusText = computed(() => |
| | | getPointEditDisplayStatusText(currentPointStatusText.value), |
| | | ) |
| | | |
| | | // 点位位置编辑是否允许修改,保持和旧点位页一致的状态口径。 |
| | | const isPointEditLocationLocked = computed(() => { |
| | |
| | | }) |
| | | |
| | | /** |
| | | * 根据围栏修改的影响范围,生成提交时的二次确认提示文案 |
| | | * @param disabledPointCount - 因移出围栏而将被停用的点位数量 |
| | | * @returns 拼接好的确认提示文案(返回一个渲染函数以支持高亮)。如果没有任何移出影响,则返回空字符串 |
| | | * 根据围栏几何变更及影响范围,生成提交时的统一确认提示文案 |
| | | * @returns 拼接好的确认提示文案(返回一个渲染函数以支持高亮)。如果没有围栏区域调整,则返回空字符串 |
| | | */ |
| | | function buildFenceSubmitConfirmMessage(disabledPointCount = 0) { |
| | | // 1. 从状态中获取本次修改将要移出的【点位数量】和【场站数量】 |
| | | function buildFenceSubmitConfirmMessage(hasGeometryChanged: boolean) { |
| | | if (!hasGeometryChanged) |
| | | return '' |
| | | const pointCount = fenceImpactDeltaSummary.value.remove.pointCount |
| | | const stationCount = fenceImpactDeltaSummary.value.remove.stationCount |
| | | |
| | | // 2. 如果没有任何点位和场站被移出,直接返回空字符串 |
| | | if (!pointCount && !stationCount) |
| | | return '' |
| | | |
| | | // 3. 返回一个渲染函数,使用 h 函数生成虚拟 DOM 以支持文案的高亮显示 |
| | | return () => { |
| | | const parts = [] |
| | | parts.push('因围栏调整,有 ') |
| | | |
| | | // 拼接点位和场站高亮片段 |
| | | if (pointCount > 0) { |
| | | parts.push(h('span', { style: 'color: #0068FF;' }, `${pointCount}个点位`)) |
| | | if (pointCount > 0 || stationCount > 0) { |
| | | parts.push('因围栏调整,有 ') |
| | | if (pointCount > 0) |
| | | parts.push(h('span', { style: 'color: #0068FF;' }, `${pointCount}个点位`)) |
| | | if (pointCount > 0 && stationCount > 0) |
| | | parts.push('、') |
| | | if (stationCount > 0) |
| | | parts.push(h('span', { style: 'color: #0068FF;' }, `${stationCount}个场站`)) |
| | | parts.push(' 将变更为“已停用”,同时围栏将回归“待测试”状态,确认发布本次修改?') |
| | | } |
| | | if (pointCount > 0 && stationCount > 0) { |
| | | parts.push('、') |
| | | else { |
| | | parts.push('因围栏调整,围栏将回归“待测试”状态,确认发布本次修改?') |
| | | } |
| | | if (stationCount > 0) { |
| | | parts.push(h('span', { style: 'color: #0068FF;' }, `${stationCount}个场站`)) |
| | | } |
| | | |
| | | parts.push(' 将变更为“已停用”,确认发布本次修改?') |
| | | return h('span', null, parts) |
| | | } |
| | | } |
| | |
| | | })) |
| | | |
| | | const pointButtonConfig = computed(() => { |
| | | const verificationStatus = pointCreateMode.value |
| | | ? Number(pointCreateStatusState.value.verificationStatus) |
| | | : Number(pointDetailData.value?.verificationStatus) |
| | | const operationStatus = pointCreateMode.value |
| | | ? Number(pointCreateStatusState.value.operationStatus) |
| | | : Number(pointDetailData.value?.operationStatus) |
| | | if (!pointCreateMode.value && !pointDetailData.value) { |
| | | return { |
| | | showVerifyButtons: false, |
| | |
| | | showNextStep: false, |
| | | } |
| | | } |
| | | const displayStatus = currentPointDisplayStatusText.value |
| | | return { |
| | | showVerifyButtons: verificationStatus === 2, |
| | | showStopButton: verificationStatus === 5 && operationStatus === 2, |
| | | showRestartButton: |
| | | verificationStatus === 4 |
| | | || (verificationStatus === 5 |
| | | && (operationStatus === 3 || operationStatus === 4)), |
| | | showNextStep: |
| | | verificationStatus === 1 |
| | | || verificationStatus === 3 |
| | | || (verificationStatus === 5 && operationStatus === 1), |
| | | showVerifyButtons: false, |
| | | showStopButton: ['可运营', '待生效', '启用中'].includes(displayStatus), |
| | | showRestartButton: ['测试不通过', '已停用', '已失效'].includes(displayStatus), |
| | | showNextStep: ['可运营', '待生效'].includes(displayStatus), |
| | | } |
| | | }) |
| | | |
| | |
| | | return [ |
| | | { label: '点位ID', value: raw.pointNumber || selectedItem.value.id }, |
| | | { label: '点位名称', value: selectedItem.value.name }, |
| | | { label: '状态', value: currentPointStatusText.value }, |
| | | { label: '状态', value: currentPointDisplayStatusText.value }, |
| | | { label: '点位地址', value: raw.pointAddress || '-' }, |
| | | { label: '限停时长', value: `${raw.parkingTimeLimit || '-'}分钟` }, |
| | | { |
| | |
| | | const stationId = String(item?.id || '') |
| | | if (!stationId) |
| | | return false |
| | | if (isStationHiddenOnMap(item)) |
| | | return false |
| | | return ( |
| | | activeTab.value === 'station' |
| | | && !isStationFormMode.value |
| | |
| | | } |
| | | |
| | | // 场站渲染规则: |
| | | // 1. 无关联围栏时保持原有渲染; |
| | | // 2. 有关联围栏时,默认跟随父围栏可见性; |
| | | // 3. 用户主动点中的当前场站始终允许回显。 |
| | | // 1. 停用/失效场站默认不展示; |
| | | // 2. 无关联围栏时保持原有渲染; |
| | | // 3. 有关联围栏时,默认跟随父围栏可见性; |
| | | // 4. 用户主动点中的当前场站仅在“可展示状态”下允许回显。 |
| | | function shouldRenderStationOnMap(item?: ListItem | null) { |
| | | if (!item) |
| | | return false |
| | | if (isStationHiddenOnMap(item)) |
| | | return false |
| | | if (shouldRenderSelectedStationOnMap(item)) |
| | | return true |
| | |
| | | polygonPath, |
| | | raw: detail, |
| | | } |
| | | await waitForServerRefreshDelay() |
| | | await loadStationData() |
| | | // 当接口已执行“停用场站并联动停用点位”时,需重拉点位数据。 |
| | | // 这样后续 renderMapEntities 才能基于最新状态刷新地图点位颜色、筛选和选中态。 |
| | |
| | | // 取消点位编辑。 |
| | | // 编辑已有点位时回退到详情态;新建点位时退出创建并清理临时点位状态。 |
| | | async function handleCancelPointEdit() { |
| | | if (pointCreateMode.value && !(await confirmDiscardPointEdit())) |
| | | if (!(await confirmDiscardPointEdit())) |
| | | return |
| | | const restoredStatus = !pointCreateMode.value |
| | | ? resolvePointStatusForCancel() |
| | | : null |
| | | const restoredDetail = !pointCreateMode.value && pointDetailData.value |
| | | ? { |
| | | ...pointDetailData.value, |
| | | operationStatus: |
| | | restoredStatus?.operationStatus ?? pointDetailData.value.operationStatus, |
| | | verificationStatus: |
| | | restoredStatus?.verificationStatus |
| | | ?? pointDetailData.value.verificationStatus, |
| | | } |
| | | : pointDetailData.value |
| | | const workflowState = buildPointCancelEditState({ |
| | | isCreateMode: pointCreateMode.value, |
| | | detail: pointDetailData.value, |
| | | detail: restoredDetail, |
| | | fallbackPointPictures: selectedItem.value?.raw?.pointPictures, |
| | | defaultPointImage: DEFAULT_POINT_IMAGE, |
| | | }) |
| | |
| | | pointAddressQuery.value = workflowState.addressQuery |
| | | pointAddressOptions.value = workflowState.addressOptions |
| | | uploadedImages.value = workflowState.uploadedImages |
| | | if (!pointCreateMode.value && restoredStatus && selectedId.value) { |
| | | pointDetailData.value = restoredDetail |
| | | const listItem = pointData.value.find(item => item.id === String(selectedId.value)) |
| | | if (listItem) { |
| | | if (listItem.raw) { |
| | | listItem.raw.operationStatus = restoredStatus.operationStatus |
| | | listItem.raw.verificationStatus = restoredStatus.verificationStatus |
| | | } |
| | | listItem.status = getPointStatusText( |
| | | restoredStatus.operationStatus, |
| | | restoredStatus.verificationStatus, |
| | | ) |
| | | } |
| | | } |
| | | cleanupPointEditMapState() |
| | | } |
| | | |
| | |
| | | handleEnterPointEdit() |
| | | } |
| | | |
| | | function resolvePointStatusForCancel() { |
| | | const rawPoint = selectedItem.value?.raw as Record<string, any> | undefined |
| | | const rawOperationStatus = Number(rawPoint?.operationStatus) |
| | | const rawVerificationStatus = Number(rawPoint?.verificationStatus) |
| | | if ( |
| | | Number.isFinite(rawOperationStatus) |
| | | && Number.isFinite(rawVerificationStatus) |
| | | ) { |
| | | return { |
| | | operationStatus: rawOperationStatus, |
| | | verificationStatus: rawVerificationStatus, |
| | | } |
| | | } |
| | | |
| | | try { |
| | | const snapshot = pointEditSnapshot.value |
| | | ? JSON.parse(pointEditSnapshot.value) |
| | | : null |
| | | const snapshotOperationStatus = Number(snapshot?.operationStatus) |
| | | const snapshotVerificationStatus = Number(snapshot?.verificationStatus) |
| | | if ( |
| | | Number.isFinite(snapshotOperationStatus) |
| | | && Number.isFinite(snapshotVerificationStatus) |
| | | ) { |
| | | return { |
| | | operationStatus: snapshotOperationStatus, |
| | | verificationStatus: snapshotVerificationStatus, |
| | | } |
| | | } |
| | | } |
| | | catch (error) { |
| | | console.warn('解析点位编辑快照失败:', error) |
| | | } |
| | | |
| | | return null |
| | | } |
| | | |
| | | function handleVerifyStatusUpdate(status: number) { |
| | | if (!pointCreateMode.value && !pointDetailData.value) |
| | | return |
| | |
| | | function handleNextStep() { |
| | | if (!pointCreateMode.value && !pointDetailData.value) |
| | | return |
| | | const v = pointCreateMode.value |
| | | ? Number(pointCreateStatusState.value.verificationStatus) |
| | | : Number(pointDetailData.value?.verificationStatus) |
| | | const o = pointCreateMode.value |
| | | ? Number(pointCreateStatusState.value.operationStatus) |
| | | : Number(pointDetailData.value?.operationStatus) |
| | | let nextV = v |
| | | let nextO = o |
| | | |
| | | // verificationStatus 非 5 时,按钮和流转都仅按 verificationStatus 判断; |
| | | // 流转时顺带把 operationStatus 归一到正常链路,避免脏数据继续传递。 |
| | | if (v === 1) { |
| | | nextV = 2 |
| | | nextO = 1 |
| | | } |
| | | else if (v === 3) { |
| | | nextV = 5 |
| | | nextO = 1 |
| | | } |
| | | else if (v === 5 && o === 1) { |
| | | nextO = 2 |
| | | } |
| | | else { |
| | | const displayStatus = currentPointDisplayStatusText.value |
| | | if (displayStatus === '可运营') { |
| | | applyLocalPointStatus(1, 5) |
| | | return |
| | | } |
| | | |
| | | applyLocalPointStatus(nextO, nextV) |
| | | if (displayStatus === '待生效') |
| | | applyLocalPointStatus(2, 5) |
| | | } |
| | | |
| | | async function handleStopPoint() { |
| | |
| | | if ( |
| | | !(await openConfirmDialog( |
| | | '是否确认重启当前点位?', |
| | | '重启点位后,当前点位将恢复到待测试状态,确定重启吗?', |
| | | '重启点位后,当前点位将恢复到可运营状态,确定重启吗?', |
| | | )) |
| | | ) { |
| | | return |
| | | } |
| | | applyLocalPointStatus(1, 1) |
| | | applyLocalPointStatus(1, 3) |
| | | } |
| | | |
| | | async function handleDeleteCurrentPoint() { |
| | |
| | | pointEditSnapshot.value = '' |
| | | selectedCreateTool.value = '' |
| | | cleanupPointEditMapState() |
| | | await waitForServerRefreshDelay() |
| | | await loadPointResources() |
| | | // 正式工作台保存后统一关闭右侧详情,避免继续展示可能已失效的详情数据。 |
| | | const workflowState = buildPointClosePanelState({ |
| | |
| | | } |
| | | const hasGeometryChanged |
| | | = !fenceCreateMode.value && hasFenceGeometryChanged() |
| | | if ( |
| | | hasGeometryChanged |
| | | && !(await openConfirmDialog( |
| | | '提示', |
| | | '已修改围栏边界或禁行区边界,保存后围栏将重回测试状态,确定保存吗?', |
| | | { |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | }, |
| | | )) |
| | | ) { |
| | | return |
| | | } |
| | | |
| | | fenceSaving.value = true |
| | | try { |
| | |
| | | = saveType === 1 && !hasGeometryChanged |
| | | ? await resolveFencePointRelationPayload() |
| | | : { addPointIdList: [], delPointIdList: [], disablePointIdList: [] } |
| | | const disabledPointCount |
| | | = fencePointRelationPayload.disablePointIdList.length |
| | | const fenceSubmitConfirmMessage |
| | | = saveType === 1 && !hasGeometryChanged |
| | | ? buildFenceSubmitConfirmMessage(disabledPointCount) |
| | | = saveType === 1 |
| | | ? buildFenceSubmitConfirmMessage(hasGeometryChanged) |
| | | : '' |
| | | if (fenceSubmitConfirmMessage) { |
| | | const confirmed = await openConfirmDialog( |
| | |
| | | fenceEditSnapshot.value = '' |
| | | selectedCreateTool.value = '' |
| | | cleanupFenceEditorState() |
| | | await waitForServerRefreshDelay() |
| | | await loadFenceData() |
| | | // 正式工作台保存后统一关闭右侧详情,避免继续展示可能已失效的详情数据。 |
| | | const workflowState = buildFenceClosePanelState() |
| | |
| | | if (!selectedItem.value) |
| | | return |
| | | |
| | | if ( |
| | | selectedItem.value.type === 'station' |
| | | && !shouldRenderStationOnMap(selectedItem.value) |
| | | ) { |
| | | return |
| | | } |
| | | |
| | | if (selectedItem.value.type === 'station' && selectedStationPointId.value) { |
| | | const stationPoint = getStationSelectedPoint() |
| | | if ( |
| | |
| | | :is-searching-address="isSearchingPointAddress" |
| | | :uploaded-images="uploadedImages" |
| | | :detail-rows="detailRows" |
| | | :current-point-status-text="currentPointStatusText" |
| | | :current-point-status-text="currentPointDisplayStatusText" |
| | | :status-meta="{ |
| | | verificationStatus: pointCreateMode |
| | | ? pointCreateStatusState.verificationStatus |