Merge remote-tracking branch 'origin/master'
32 files modified
1 files renamed
14 files deleted
7 files added
| | |
| | | //center.javablade.com/api/packages/blade/npm/:_authToken=66c6549d47896da834ef4a84b5fcbf4763744b28 |
| | | registry="https://registry.npmmirror.com" |
| | | #依赖开启严格模式 |
| | | strict-peer-dependencies=true |
| | | strict-peer-dependencies=false |
| | | # 自动安装对等依赖,无需手动安装 |
| | | auto-install-peers=true |
| | | # 允许所有包执行构建脚本(避免新依赖安装后无法运行)pnpm9的坑 |
| | |
| | | # @Author : yuan |
| | | # @Date : 2026-01-31 14:34:31 |
| | | # @LastEditors : yuan |
| | | # @LastEditTime : 2026-01-31 15:18:17 |
| | | # @LastEditTime : 2026-02-02 11:01:46 |
| | | # @FilePath : \applications\drone-command\env\.env.development |
| | | # @Description : |
| | | # Copyright 2026 OBKoro1, All Rights Reserved. |
| | |
| | | |
| | | #开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖) |
| | | # VITE_APP_URL=https://wrj.shuixiongit.com/api |
| | | VITE_APP_URL=http://192.168.1.33 |
| | | VITE_APP_URL=http://192.168.1.168 |
| | | |
| | | #新大屏地址 |
| | | VITE_APP_DASHBOARD_URL='https://wrj.shuixiongit.com/command-center-dashboard/' |
| | |
| | | * @Author : yuan |
| | | * @Date : 2026-01-09 11:14:04 |
| | | * @LastEditors : yuan |
| | | * @LastEditTime : 2026-01-26 19:02:07 |
| | | * @LastEditTime : 2026-02-02 09:45:21 |
| | | * @FilePath : \applications\drone-command\src\api\dataCockpit\index.js |
| | | * @Description : |
| | | * Copyright 2026 OBKoro1, All Rights Reserved. |
| | |
| | | }) |
| | | } |
| | | |
| | | // 最新 --- 数据驾驶舱地图聚合 |
| | | export const newCockpitAggregationApi = (params) => { |
| | | return request({ |
| | | url: '/drone-fw/cockpit/dataCockpit/statisticalDeviceOut', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // 区域列表 |
| | | export const areaDivideListApi = (params) => { |
| | | return request({ |
| | |
| | | |
| | | import { fwDefenseSceneManageListApi } from '@/views/areaManage/sceneManage/sceneManageApi' |
| | | |
| | | import { cockpitAggregationApi } from '@/api/dataCockpit' |
| | | import { newCockpitAggregationApi } from '@/api/dataCockpit' |
| | | import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png' |
| | | import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png' |
| | | import offlineEquipmentIcon from '@/assets/images/dataCockpit/map/offline-equipment.png' |
| | |
| | | const CLUSTER_HEIGHT = 100000 |
| | | const DETAIL_HEIGHT = 10000 |
| | | const POLYGON_HEIGHT_M = 0.2 |
| | | const DRONE_TRACK_DURATION_S = 30 * 60 |
| | | |
| | | const props = defineProps({ |
| | | allDevices: { |
| | |
| | | let droneTrackBillboardCollection = null |
| | | let droneTrackPolylineCollection = null |
| | | let droneTrackTickHandler = null |
| | | let droneTrackRafId = null |
| | | let droneTrackStartTime = null |
| | | let droneTrackAnimStartAt = 0 |
| | | let droneTrackLastTickAt = 0 |
| | | let droneTrackRuntime = [] |
| | | let droneTrackSource = null |
| | | let mapReadyHandled = false |
| | | let favoritePulseRafId = null |
| | | let favoritePulseStartAt = 0 |
| | | let favoritePulseElapsed = 0 |
| | | const favoritePulseEntities = [] |
| | | const pulseBaseColor = Cesium.Color.fromCssColorString('#FF3B30') |
| | | const PULSE_MIN_RADIUS_M = 60 |
| | | const PULSE_MAX_RADIUS_M = 180 |
| | | const PULSE_DURATION_S = 1.6 |
| | | const PULSE_MIN_RADIUS_M = 2 |
| | | const PULSE_MAX_RADIUS_M = 30 |
| | | const PULSE_DURATION_S = 1.4 |
| | | const detailVisible = ref(true) |
| | | const clusterVisible = ref(false) |
| | | const countyCenterMap = new Map() |
| | |
| | | return canvas |
| | | } |
| | | |
| | | class PulseSphereMaterialProperty { |
| | | constructor(color, speed = 1.2, bands = 3.0, softness = 0.18) { |
| | | this._definitionChanged = new Cesium.Event() |
| | | this.color = color |
| | | this.speed = speed |
| | | this.bands = bands |
| | | this.softness = softness |
| | | } |
| | | get isConstant() { |
| | | return true |
| | | } |
| | | get definitionChanged() { |
| | | return this._definitionChanged |
| | | } |
| | | getType() { |
| | | return 'PulseSphereMaterial' |
| | | } |
| | | getValue(_time, result = {}) { |
| | | result.color = this.color |
| | | result.time = (performance.now() / 1000) * this.speed |
| | | result.bands = this.bands |
| | | result.softness = this.softness |
| | | return result |
| | | } |
| | | equals(other) { |
| | | return ( |
| | | other instanceof PulseSphereMaterialProperty && |
| | | Cesium.Color.equals(this.color, other.color) && |
| | | this.speed === other.speed && |
| | | this.bands === other.bands && |
| | | this.softness === other.softness |
| | | ) |
| | | } |
| | | } |
| | | |
| | | let pulseSphereMaterial = null |
| | | const getPulseSphereMaterial = () => { |
| | | if (pulseSphereMaterial) return pulseSphereMaterial |
| | | Cesium.Material._materialCache.addMaterial('PulseSphereMaterial', { |
| | | fabric: { |
| | | type: 'PulseSphereMaterial', |
| | | uniforms: { |
| | | color: pulseBaseColor.withAlpha(0.45), |
| | | time: 0, |
| | | bands: 3.0, |
| | | softness: 0.18, |
| | | }, |
| | | source: ` |
| | | czm_material czm_getMaterial(czm_materialInput materialInput) |
| | | { |
| | | czm_material material = czm_getDefaultMaterial(materialInput); |
| | | vec3 normalEC = normalize(materialInput.normalEC); |
| | | vec3 viewEC = normalize(-materialInput.positionToEyeEC); |
| | | float ndv = max(dot(normalEC, viewEC), 0.0); |
| | | float fresnel = pow(1.0 - ndv, 1.6); |
| | | float wave = sin((ndv * bands + time) * 6.2831853); |
| | | float ring = smoothstep(-softness, softness, wave); |
| | | float alpha = (ring * 0.6 + fresnel * 0.4) * color.a; |
| | | material.diffuse = color.rgb; |
| | | material.alpha = alpha; |
| | | return material; |
| | | } |
| | | `, |
| | | }, |
| | | translucent: () => true, |
| | | }) |
| | | pulseSphereMaterial = new PulseSphereMaterialProperty(pulseBaseColor.withAlpha(0.45), 1.1, 3.2, 0.22) |
| | | return pulseSphereMaterial |
| | | } |
| | | |
| | | const createFavoritePulseEntity = (billboard) => { |
| | | if (!viewer || !billboard) return |
| | | const material = new Cesium.ImageMaterialProperty({ |
| | | image: getPulseCanvas(), |
| | | transparent: true, |
| | | }) |
| | | const material = getPulseSphereMaterial() |
| | | const pulse = { |
| | | material, |
| | | offset: Math.random() * PULSE_DURATION_S, |
| | | entity: null, |
| | | } |
| | | const getPulseRadius3D = () => { |
| | | const radius = getPulseRadius(pulse) |
| | | return new Cesium.Cartesian3(radius, radius, radius) |
| | | } |
| | | pulse.entity = viewer.entities.add({ |
| | | position: new Cesium.CallbackProperty(() => billboard.position, false), |
| | | ellipse: { |
| | | semiMajorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false), |
| | | semiMinorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false), |
| | | position: new Cesium.CallbackProperty(() => getBillboardPosition(billboard), false), |
| | | ellipsoid: { |
| | | radii: new Cesium.CallbackProperty(() => getPulseRadius3D(), false), |
| | | material, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | }, |
| | | }) |
| | | favoritePulseEntities.push(pulse) |
| | | } |
| | | |
| | | const getBillboardPosition = billboard => { |
| | | if (!viewer || !billboard) return null |
| | | const position = billboard.position |
| | | if (position?.getValue) { |
| | | return position.getValue(viewer.clock.currentTime) |
| | | } |
| | | return position |
| | | } |
| | | |
| | | const getDevicePosition = item => { |
| | |
| | | const setDroneVisibility = visible => { |
| | | if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible |
| | | if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible |
| | | if (droneTrackSource) droneTrackSource.show = visible |
| | | if (favoritePulseEntities.length) { |
| | | favoritePulseEntities.forEach(pulse => { |
| | | if (pulse?.entity) pulse.entity.show = visible |
| | |
| | | } |
| | | droneTrackPolylineCollection = null |
| | | } |
| | | if (droneTrackSource) { |
| | | viewer.dataSources.remove(droneTrackSource) |
| | | droneTrackSource = null |
| | | } |
| | | droneTrackRuntime = [] |
| | | if (favoritePulseEntities.length) { |
| | | favoritePulseEntities.forEach(pulse => { |
| | |
| | | } |
| | | } |
| | | |
| | | const ensureDroneTrackSource = () => { |
| | | if (!viewer) return |
| | | if (!droneTrackSource) { |
| | | droneTrackSource = new Cesium.CustomDataSource('droneTrackSource') |
| | | viewer.dataSources.add(droneTrackSource) |
| | | } |
| | | } |
| | | |
| | | const setupDroneTrackClock = () => { |
| | | if (!viewer) return |
| | | const startTime = Cesium.JulianDate.now() |
| | | const stopTime = Cesium.JulianDate.addSeconds(startTime, DRONE_TRACK_DURATION_S, new Cesium.JulianDate()) |
| | | viewer.clock.startTime = startTime |
| | | viewer.clock.stopTime = stopTime |
| | | viewer.clock.currentTime = startTime |
| | | viewer.clock.multiplier = 1 |
| | | viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER |
| | | viewer.clock.clockRange = Cesium.ClockRange.CLAMPED |
| | | } |
| | | |
| | | const updateDroneTrackPositions = elapsed => { |
| | | if (!viewer || !viewer.scene || !droneTrackRuntime.length) return |
| | | droneTrackRuntime.forEach(track => { |
| | | if (!track?.billboard || track.billboard.isDestroyed?.()) return |
| | | if (!track?.polyline) return |
| | | if (!track.positions || track.positions.length < 2) return |
| | | const duration = track.duration |
| | | if (duration <= 0) return |
| | | const t = Math.min(Math.max(elapsed, 0), duration) |
| | | const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration)) |
| | | const ratio = (t - seg * track.segmentDuration) / track.segmentDuration |
| | | const isEntityPosition = typeof track.billboard?.position?.getValue === 'function' |
| | | const pos = isEntityPosition |
| | | ? getBillboardPosition(track.billboard) |
| | | : Cesium.Cartesian3.lerp( |
| | | track.positions[seg], |
| | | track.positions[seg + 1], |
| | | ratio, |
| | | new Cesium.Cartesian3() |
| | | ) |
| | | if (!pos) return |
| | | if (!isEntityPosition) { |
| | | track.billboard.position = pos |
| | | } |
| | | const pathCount = Math.min(track.positions.length - 1, Math.floor(t / track.segmentDuration)) |
| | | const pathPositions = track.positions.slice(0, pathCount + 1) |
| | | track.polyline.positions = [...pathPositions, pos] |
| | | }) |
| | | } |
| | | |
| | | const startDroneTrackAnimation = () => { |
| | | if (!viewer) return |
| | | stopDroneTrackAnimation() |
| | | droneTrackStartTime = Cesium.JulianDate.now() |
| | | droneTrackTickHandler = clock => { |
| | | if (!viewer || viewer.isDestroyed?.() || !viewer.scene) { |
| | | stopDroneTrackAnimation() |
| | | droneTrackStartTime = viewer.clock.startTime |
| | | viewer.clock.shouldAnimate = true |
| | | droneTrackAnimStartAt = performance.now() |
| | | droneTrackLastTickAt = droneTrackAnimStartAt |
| | | const renderTick = now => { |
| | | if (!viewer || viewer.isDestroyed?.()) { |
| | | droneTrackRafId = null |
| | | return |
| | | } |
| | | if (!droneTrackRuntime.length) return |
| | | const current = clock.currentTime |
| | | const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime) |
| | | droneTrackRuntime.forEach(track => { |
| | | if (!track?.billboard || track.billboard.isDestroyed?.()) return |
| | | if (!track.billboard._scene) return |
| | | if (!track.positions || track.positions.length < 2) return |
| | | const duration = track.duration |
| | | if (duration <= 0) return |
| | | const t = ((elapsed % duration) + duration) % duration |
| | | const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration)) |
| | | const ratio = (t - seg * track.segmentDuration) / track.segmentDuration |
| | | const pos = Cesium.Cartesian3.lerp( |
| | | track.positions[seg], |
| | | track.positions[seg + 1], |
| | | ratio, |
| | | new Cesium.Cartesian3() |
| | | const deltaSeconds = Math.max(0, (now - droneTrackLastTickAt) / 1000) |
| | | droneTrackLastTickAt = now |
| | | if (viewer.clock?.currentTime && viewer.clock?.stopTime) { |
| | | const nextTime = Cesium.JulianDate.addSeconds( |
| | | viewer.clock.currentTime, |
| | | deltaSeconds * (viewer.clock.multiplier || 1), |
| | | new Cesium.JulianDate() |
| | | ) |
| | | track.billboard.position = pos |
| | | }) |
| | | if (Cesium.JulianDate.greaterThan(nextTime, viewer.clock.stopTime)) { |
| | | viewer.clock.currentTime = viewer.clock.stopTime |
| | | } else { |
| | | viewer.clock.currentTime = nextTime |
| | | } |
| | | } |
| | | const elapsed = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, droneTrackStartTime) |
| | | updateDroneTrackPositions(elapsed) |
| | | viewer.scene?.requestRender?.() |
| | | droneTrackRafId = requestAnimationFrame(renderTick) |
| | | } |
| | | viewer.clock.onTick.addEventListener(droneTrackTickHandler) |
| | | viewer.clock.shouldAnimate = true |
| | | droneTrackRafId = requestAnimationFrame(renderTick) |
| | | } |
| | | |
| | | const stopDroneTrackAnimation = () => { |
| | | if (!viewer || !droneTrackTickHandler) return |
| | | viewer.clock.onTick.removeEventListener(droneTrackTickHandler) |
| | | droneTrackTickHandler = null |
| | | if (droneTrackRafId) { |
| | | cancelAnimationFrame(droneTrackRafId) |
| | | droneTrackRafId = null |
| | | } |
| | | } |
| | | |
| | | const startFavoritePulseAnimation = () => { |
| | |
| | | clearDroneTrackEntities() |
| | | if (!list?.length) return |
| | | ensureDroneTrackCollections() |
| | | ensureDroneTrackSource() |
| | | setupDroneTrackClock() |
| | | droneTrackBillboardCollection.show = detailVisible.value |
| | | droneTrackPolylineCollection.show = detailVisible.value |
| | | droneTrackRuntime = [] |
| | | const segmentDuration = 6 |
| | | const baseTrackColor = Cesium.Color.fromCssColorString('red') |
| | | ; (list || []).forEach((item, trackIndex) => { |
| | | const position = getDevicePosition(item) |
| | |
| | | const positions = points.map(point => |
| | | Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height) |
| | | ) |
| | | const segmentDuration = DRONE_TRACK_DURATION_S / Math.max(positions.length - 1, 1) |
| | | let trackMaterial = createDroneTrackMaterial({ |
| | | color: baseTrackColor, |
| | | speed: 4.5, |
| | |
| | | if (!trackMaterial) { |
| | | trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor }) |
| | | } |
| | | droneTrackPolylineCollection.add({ |
| | | positions, |
| | | const polyline = droneTrackPolylineCollection.add({ |
| | | positions: [positions[0]], |
| | | width: 3, |
| | | material: trackMaterial, |
| | | }) |
| | | const droneId = `drone-alarm-${item?.alarmRecordId ?? item?.id ?? trackIndex}` |
| | | const billboard = droneTrackBillboardCollection.add({ |
| | | position: positions[positions.length - 1], |
| | | image: droneIcon, |
| | | width: 36, |
| | | height: 36, |
| | | verticalOrigin: Cesium.VerticalOrigin.CENTER, |
| | | disableDepthTestDistance: Number.POSITIVE_INFINITY, |
| | | const startTime = viewer.clock.startTime |
| | | const stopTime = viewer.clock.stopTime |
| | | const positionProperty = new Cesium.SampledPositionProperty() |
| | | positions.forEach((pos, index) => { |
| | | const sampleTime = Cesium.JulianDate.addSeconds( |
| | | startTime, |
| | | index * segmentDuration, |
| | | new Cesium.JulianDate() |
| | | ) |
| | | positionProperty.addSample(sampleTime, pos) |
| | | }) |
| | | billboard.id = droneId |
| | | positionProperty.setInterpolationOptions({ |
| | | interpolationDegree: 1, |
| | | interpolationAlgorithm: Cesium.LinearApproximation, |
| | | }) |
| | | const entity = droneTrackSource.entities.add({ |
| | | id: droneId, |
| | | availability: new Cesium.TimeIntervalCollection([ |
| | | new Cesium.TimeInterval({ |
| | | start: startTime, |
| | | stop: stopTime, |
| | | }), |
| | | ]), |
| | | position: positionProperty, |
| | | orientation: new Cesium.VelocityOrientationProperty(positionProperty), |
| | | billboard: { |
| | | image: droneIcon, |
| | | width: 36, |
| | | height: 36, |
| | | verticalOrigin: Cesium.VerticalOrigin.CENTER, |
| | | disableDepthTestDistance: Number.POSITIVE_INFINITY, |
| | | }, |
| | | }) |
| | | const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration) |
| | | dronePickMap.set(droneId, { |
| | | data: { |
| | | ...item, |
| | | flightHeightM: item.flightHeightM ?? points[0].height, |
| | | flightSpeedMs: item.flightSpeedMs ?? speedMs, |
| | | longitude: item.longitude ?? points[0].longitude, |
| | | latitude: item.latitude ?? points[0].latitude, |
| | | }, |
| | | billboard, |
| | | }) |
| | | if (isFavorited(item)) { |
| | | createFavoritePulseEntity(billboard) |
| | | } |
| | | dronePickMap.set(droneId, { |
| | | data: { |
| | | ...item, |
| | | flightHeightM: item.flightHeightM ?? points[0].height, |
| | | flightSpeedMs: item.flightSpeedMs ?? speedMs, |
| | | longitude: item.longitude ?? points[0].longitude, |
| | | latitude: item.latitude ?? points[0].latitude, |
| | | }, |
| | | billboard: entity, |
| | | }) |
| | | if (isFavorited(item)) { |
| | | createFavoritePulseEntity(entity) |
| | | } |
| | | droneTrackRuntime.push({ |
| | | positions, |
| | | billboard, |
| | | polyline, |
| | | billboard: entity, |
| | | segmentDuration, |
| | | duration: (points.length - 1) * segmentDuration, |
| | | duration: DRONE_TRACK_DURATION_S, |
| | | }) |
| | | }) |
| | | viewer.clock.multiplier = 1 |
| | | startDroneTrackAnimation() |
| | | startFavoritePulseAnimation() |
| | | reorderCockpitPrimitives() |
| | |
| | | |
| | | const updatePopupPosition = () => { |
| | | if (!viewer || !selectedDeviceBillboard) return |
| | | const cartesian = selectedDeviceBillboard.position |
| | | const cartesian = getBillboardPosition(selectedDeviceBillboard) |
| | | if (!cartesian) return |
| | | const screenPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian) |
| | | if (!screenPosition) return |
| | |
| | | |
| | | const loadAggregation = async () => { |
| | | try { |
| | | const res = await cockpitAggregationApi({ |
| | | const res = await newCockpitAggregationApi({ |
| | | effectiveRangeKmIsNotNull: 1 |
| | | }) |
| | | renderAggregation(res?.data?.data ?? []) |
| | |
| | | import * as Cesium from 'cesium' |
| | | import { MapTooltip } from '@ztzf/utils' |
| | | import { ToolBase } from '../ToolBase' |
| | | import { buildEllipsePositions } from '../shapeUtils' |
| | | import { |
| | | buildEllipsePositions, |
| | | formatBufferRadius, |
| | | getBufferRadiusLabelStyle, |
| | | getBufferRadiusPoint, |
| | | } from '../shapeUtils' |
| | | |
| | | export class DrawBufferCircleTool extends ToolBase { |
| | | constructor(viewer) { |
| | |
| | | this.radiusPoint1 = null |
| | | this.radiusPoint2 = null |
| | | this.radiusPoint3 = null |
| | | this.radiusLevel1LabelEntity = null |
| | | this.radiusLevel2LabelEntity = null |
| | | this.radiusLevel3LabelEntity = null |
| | | this.isDrawing = false |
| | | this.drawStep = 0 |
| | | this.isCompleted = false |
| | |
| | | material: Cesium.Color.fromBytes(0, 251, 255, 255), |
| | | }, |
| | | }) |
| | | |
| | | this.radiusLevel1LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel1) |
| | | this.radiusLevel2LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel2) |
| | | this.radiusLevel3LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel3) |
| | | } |
| | | |
| | | clearPreviewEntities() { |
| | |
| | | this.circleLevel1OutlineEntity = null |
| | | this.circleLevel2OutlineEntity = null |
| | | this.circleLevel3OutlineEntity = null |
| | | this.radiusLevel1LabelEntity = null |
| | | this.radiusLevel2LabelEntity = null |
| | | this.radiusLevel3LabelEntity = null |
| | | } |
| | | |
| | | getSurfaceDistance(startCartesian, endCartesian) { |
| | |
| | | const cartesian = scene.pickPosition(screenPosition) |
| | | if (cartesian) return cartesian |
| | | return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid) |
| | | } |
| | | |
| | | getRadiusPoint(radius) { |
| | | return getBufferRadiusPoint(this.centerCartesian, radius) |
| | | } |
| | | |
| | | createRadiusLabelEntity(getRadius) { |
| | | return this.dataSource.entities.add({ |
| | | position: new Cesium.CallbackProperty( |
| | | () => getBufferRadiusPoint(this.centerCartesian, getRadius()), |
| | | false |
| | | ), |
| | | label: { |
| | | ...getBufferRadiusLabelStyle(), |
| | | text: new Cesium.CallbackProperty(() => formatBufferRadius(getRadius()), false), |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | destroy() { |
| | |
| | | this.radiusPoint1 = null |
| | | this.radiusPoint2 = null |
| | | this.radiusPoint3 = null |
| | | this.radiusLevel1LabelEntity = null |
| | | this.radiusLevel2LabelEntity = null |
| | | this.radiusLevel3LabelEntity = null |
| | | this.isDrawing = false |
| | | this.drawStep = 0 |
| | | this.isCompleted = false |
| | |
| | | import * as Cesium from 'cesium' |
| | | import { MapTooltip } from '@ztzf/utils' |
| | | import { ToolBase } from '../ToolBase' |
| | | import { buildEllipsePositions, cartesianToLocal } from '../shapeUtils' |
| | | import { |
| | | buildEllipsePositions, |
| | | cartesianToLocal, |
| | | formatBufferRadius, |
| | | getBufferRadiusLabelStyle, |
| | | getBufferRadiusPoint, |
| | | } from '../shapeUtils' |
| | | |
| | | const normalizePoints = points => |
| | | (points || []).map(point => ({ |
| | |
| | | this.radiusLevel1Entity = null |
| | | this.radiusLevel2Entity = null |
| | | this.radiusLevel3Entity = null |
| | | this.radiusLevel1LabelEntity = null |
| | | this.radiusLevel2LabelEntity = null |
| | | this.radiusLevel3LabelEntity = null |
| | | } |
| | | |
| | | start(data = []) { |
| | |
| | | }, |
| | | customType: 'circle-radius-3', |
| | | }) |
| | | |
| | | this.radiusLevel1LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel1) |
| | | this.radiusLevel2LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel2) |
| | | this.radiusLevel3LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel3) |
| | | } |
| | | |
| | | updateRadiusPoints() { |
| | |
| | | } |
| | | |
| | | getRadiusPoint(radius) { |
| | | const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian) |
| | | const local = new Cesium.Cartesian3(radius, 0, 0) |
| | | return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3()) |
| | | return getBufferRadiusPoint(this.centerCartesian, radius) |
| | | } |
| | | |
| | | createRadiusLabelEntity(getRadius) { |
| | | return this.dataSource.entities.add({ |
| | | position: new Cesium.CallbackProperty( |
| | | () => getBufferRadiusPoint(this.centerCartesian, getRadius()), |
| | | false |
| | | ), |
| | | label: { |
| | | ...getBufferRadiusLabelStyle(), |
| | | text: new Cesium.CallbackProperty(() => formatBufferRadius(getRadius()), false), |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | getPositionFromScreen(screenPosition) { |
| | |
| | | this.circleLevel1OutlineEntity = null |
| | | this.circleLevel2OutlineEntity = null |
| | | this.circleLevel3OutlineEntity = null |
| | | this.radiusLevel1LabelEntity = null |
| | | this.radiusLevel2LabelEntity = null |
| | | this.radiusLevel3LabelEntity = null |
| | | this.dragType = null |
| | | } |
| | | } |
| | |
| | | getCenterFromBounds, |
| | | getMetersBetween, |
| | | cartesianToLocal, |
| | | formatBufferRadius, |
| | | getBufferRadiusPoint, |
| | | getBufferRadiusLabelStyle, |
| | | addBufferRadiusLabelEntity, |
| | | } from './shapeUtils' |
| | | export { DrawPolygonTool } from './draw/DrawPolygonTool' |
| | | export { DrawRectangleTool } from './draw/DrawRectangleTool' |
| | |
| | | const inverse = Cesium.Matrix4.inverse(frame, new Cesium.Matrix4()) |
| | | return Cesium.Matrix4.multiplyByPoint(inverse, positionCartesian, new Cesium.Cartesian3()) |
| | | } |
| | | |
| | | export const formatBufferRadius = meters => { |
| | | if (!Number.isFinite(meters)) return '' |
| | | if (meters >= 1000) return `${(meters / 1000).toFixed(2)} km` |
| | | return `${meters.toFixed(2)} m` |
| | | } |
| | | |
| | | export const getBufferRadiusPoint = (centerCartesian, radius) => { |
| | | if (!centerCartesian || !Number.isFinite(radius)) return null |
| | | const frame = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian) |
| | | const local = new Cesium.Cartesian3(radius, 0, 0) |
| | | return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3()) |
| | | } |
| | | |
| | | export const getBufferRadiusLabelStyle = () => ({ |
| | | font: '14px Source Han Sans CN', |
| | | fillColor: Cesium.Color.WHITE, |
| | | outlineColor: Cesium.Color.fromBytes(24, 48, 92, 255), |
| | | outlineWidth: 2, |
| | | style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | showBackground: true, |
| | | backgroundColor: Cesium.Color.fromBytes(8, 12, 22, 180), |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
| | | pixelOffset: new Cesium.Cartesian2(0, -12), |
| | | disableDepthTestDistance: Number.POSITIVE_INFINITY, |
| | | }) |
| | | |
| | | export const addBufferRadiusLabelEntity = (dataSource, centerCartesian, radius, options = {}) => { |
| | | if (!dataSource) return null |
| | | const position = getBufferRadiusPoint(centerCartesian, radius) |
| | | if (!position) return null |
| | | const { name, customData } = options |
| | | return dataSource.entities.add({ |
| | | ...(name ? { name } : {}), |
| | | ...(customData ? { customData } : {}), |
| | | position, |
| | | label: { |
| | | ...getBufferRadiusLabelStyle(), |
| | | text: formatBufferRadius(radius), |
| | | }, |
| | | }) |
| | | } |
| | |
| | | |
| | | |
| | | ## 列表 |
| | | ## 根据多边形区域查询派出所 |
| | | |
| | | |
| | | **接口地址**:`/area/fwDefenseSceneManage/list` |
| | | **接口地址**:`/area/fwPoliceStation/queryByPolygons` |
| | | |
| | | |
| | | **请求方式**:`GET` |
| | | **请求方式**:`POST` |
| | | |
| | | |
| | | **请求数据类型**:`application/x-www-form-urlencoded` |
| | | **请求数据类型**:`application/json` |
| | | |
| | | |
| | | **响应数据类型**:`*/*` |
| | | |
| | | |
| | | **接口描述**:<p>传入time</p> |
| | | **接口描述**:<p>传入多边形WKT字符串集合</p> |
| | | |
| | | |
| | | |
| | | **请求示例**: |
| | | |
| | | |
| | | ```javascript |
| | | { |
| | | "polygons": [ |
| | | "POLYGON((114.96898636473107 27.11156483383477,114.97972069576815 27.111564833834766,114.97972069576815 27.120473921808035,114.96898636473107 27.120473921808035,114.96898636473107 27.11156483383477))" |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | |
| | | **请求参数**: |
| | |
| | | |
| | | | 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema | |
| | | | -------- | -------- | ----- | -------- | -------- | ------ | |
| | | |time|时间筛选(yyyy-MM-dd HH:mm:ss)|query|false|string(date-time)|| |
| | | |dto|派出所多边形区域查询入参|body|true|FwPoliceStationPolygonQueryDTO对象|FwPoliceStationPolygonQueryDTO对象| |
| | | |  polygons|多边形WKT字符串集合||true|array|string| |
| | | |
| | | |
| | | **响应状态**: |
| | |
| | | |
| | | | 状态码 | 说明 | schema | |
| | | | -------- | -------- | ----- | |
| | | |200|OK|R«List«FwDefenseSceneManageVO»»| |
| | | |200|OK|R«List«FwPoliceStationVO»»| |
| | | |201|Created|| |
| | | |401|Unauthorized|| |
| | | |403|Forbidden|| |
| | | |404|Not Found|| |
| | |
| | | | 参数名称 | 参数说明 | 类型 | schema | |
| | | | -------- | -------- | ----- |----- | |
| | | |code|状态码|integer(int32)|integer(int32)| |
| | | |data|承载数据|array|FwDefenseSceneManageVO| |
| | | |data|承载数据|array|FwPoliceStationVO| |
| | | |  address|派出所地址|string|| |
| | | |  areaCode|区域编码|string|| |
| | | |  defenseLeader|防控负责人|string|| |
| | | |  defenseSceneId|关联场景配置ID|integer(int64)|| |
| | | |  defenseSceneName|场景配置名称|string|| |
| | | |  effectiveDateEnd|有效时间-结束|string(date-time)|| |
| | | |  effectiveDateStart|有效时间-开始|string(date-time)|| |
| | | |  id||integer(int64)|| |
| | | |  latitude|指挥点纬度|number(double)|| |
| | | |  leaderPhone|防控负责人电话|string|| |
| | | |  longitude|指挥点经度|number(double)|| |
| | | |  sceneName|场景名称|string|| |
| | | |  contactPerson|联系人|string|| |
| | | |  contactPhone|联系方式|string|| |
| | | |  createDept|创建部门|integer(int64)|| |
| | | |  createTime|创建时间|string(date-time)|| |
| | | |  createUser|创建人|integer(int64)|| |
| | | |  id|主键id|integer(int64)|| |
| | | |  latitude|纬度|number(double)|| |
| | | |  longitude|经度|number(double)|| |
| | | |  stationName|派出所名称|string|| |
| | | |  status|状态(0正常 1停用)|integer(int32)|| |
| | | |  updateTime|更新时间|string(date-time)|| |
| | | |  updateUser|更新人|integer(int64)|| |
| | | |msg|返回消息|string|| |
| | | |success|是否成功|boolean|| |
| | | |
| | |
| | | "code": 0, |
| | | "data": [ |
| | | { |
| | | "address": "", |
| | | "areaCode": "", |
| | | "defenseLeader": "", |
| | | "defenseSceneId": 0, |
| | | "defenseSceneName": "", |
| | | "effectiveDateEnd": "", |
| | | "effectiveDateStart": "", |
| | | "contactPerson": "", |
| | | "contactPhone": "", |
| | | "createDept": 0, |
| | | "createTime": "", |
| | | "createUser": 0, |
| | | "id": 0, |
| | | "latitude": 0, |
| | | "leaderPhone": "", |
| | | "longitude": 0, |
| | | "sceneName": "" |
| | | "stationName": "", |
| | | "status": 0, |
| | | "updateTime": "", |
| | | "updateUser": 0 |
| | | } |
| | | ], |
| | | "msg": "", |
| | |
| | | <div class="right-container"> |
| | | <div class="header"> |
| | | <span>{{ titleEnum[dialogMode] }}</span> |
| | | |
| | | |
| | | <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon> |
| | | </div> |
| | | |
| | |
| | | import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi' |
| | | import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils' |
| | | import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue' |
| | | import { DrawPolygon } from '@/utils/cesium/DrawPolygon' |
| | | import { DrawPolygon } from '@ztzf/utils' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import * as turf from '@turf/turf' |
| | | import * as Cesium from 'cesium' |
| | |
| | | <div class="right-container"> |
| | | <div class="header"> |
| | | <span>{{ titleEnum[dialogMode] }}</span> |
| | | |
| | | |
| | | <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon> |
| | | </div> |
| | | |
| | |
| | | import { useRoute } from 'vue-router' |
| | | import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils' |
| | | import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue' |
| | | import { DrawPolygon } from '@/utils/cesium/DrawPolygon' |
| | | import { DrawPolygon } from '@ztzf/utils' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import * as turf from '@turf/turf' |
| | | import * as Cesium from 'cesium' |
| | |
| | | <div class="dialog-container"> |
| | | <div class="left-container"> |
| | | <CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false" |
| | | :terrain="true" :layer-mode="4" :boundary="false" /> |
| | | :terrain="true" :layer-mode="4" :boundary="false" :zoom-to-boundary="dialogMode === 'add'" /> |
| | | <div v-if="showTypePanel && !readonly" class="shape-type-panel"> |
| | | <div class="panel-header"> |
| | | <span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span> |
| | |
| | | <div class="panel-body"> |
| | | <span class="panel-label">区域类型</span> |
| | | <el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType" |
| | | placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags :multiple="currentShapeType === 'buffer'" |
| | | @change="handleAreaTypeChange"> |
| | | placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags |
| | | :multiple="currentShapeType === 'buffer'" @change="handleAreaTypeChange"> |
| | | <el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue" |
| | | :value="item.dictKey" /> |
| | | </el-select> |
| | |
| | | <script setup> |
| | | import { Close } from '@element-plus/icons-vue' |
| | | |
| | | import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' |
| | | import { computed, inject, nextTick, onBeforeUnmount, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi } from './partitionApi' |
| | | import { saveOperationLog } from '@ztzf/apis' |
| | | import { useRoute } from 'vue-router' |
| | | import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils' |
| | | import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue' |
| | | import { DrawManager, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools' |
| | | import { |
| | | DrawManager, |
| | | EditManager, |
| | | addBufferRadiusLabelEntity, |
| | | buildEllipsePositions, |
| | | } from '@/utils/cesium/shapeTools' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import { |
| | | createDeviceRangePrimitive, |
| | |
| | | } from '@/utils/cesium/deviceRange' |
| | | import * as Cesium from 'cesium' |
| | | import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants' |
| | | import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi' |
| | | import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice' |
| | | import { fwPoliceStationQueryByPolygonsApi } from '@/views/areaManage/precinctInfo/precinctInfoApi' |
| | | import { fwDeviceListByPolygonsApi } from '@/views/basicManage/deviceStock/fwDevice' |
| | | import polygonIcon from '@/assets/images/areaMap/polygon.png' |
| | | import rectIcon from '@/assets/images/areaMap/rect.png' |
| | | import ellipseIcon from '@/assets/images/areaMap/ellipse.png' |
| | |
| | | const deviceRangePrimitiveMap = new Map() |
| | | const activeToolMode = ref('') |
| | | const suppressDeviceFly = ref(false) |
| | | const isDialogInitializing = ref(false) |
| | | let lastSelectedDeviceIds = [] |
| | | let deviceRangeRenderToken = 0 |
| | | let deviceListTimer = null |
| | | let deviceListRequestToken = 0 |
| | | let policeStationListTimer = null |
| | | let policeStationRequestToken = 0 |
| | | |
| | | const rules = { |
| | | areaName: fieldRules(true, 50), |
| | |
| | | () => visible.value, |
| | | val => { |
| | | if (!val) { |
| | | if (deviceListTimer) { |
| | | clearTimeout(deviceListTimer) |
| | | deviceListTimer = null |
| | | } |
| | | if (policeStationListTimer) { |
| | | clearTimeout(policeStationListTimer) |
| | | policeStationListTimer = null |
| | | } |
| | | viewEntity && viewer?.entities?.remove(viewEntity) |
| | | viewEntity = null |
| | | clearActiveTool() |
| | |
| | | val => { |
| | | updateActiveShapeAreaType(val) |
| | | } |
| | | ) |
| | | watch( |
| | | () => shapeList.value, |
| | | () => { |
| | | if (isDialogInitializing.value) return |
| | | queueDeviceListRefresh() |
| | | queuePoliceStationRefresh() |
| | | }, |
| | | { deep: true } |
| | | ) |
| | | |
| | | // 加载详情 |
| | |
| | | viewer.scene.primitives.remove(primitive) |
| | | } |
| | | deviceRangePrimitiveMap.clear() |
| | | } |
| | | |
| | | function clearDeviceListState () { |
| | | deviceOptions.value = [] |
| | | selectedDeviceRows.value = [] |
| | | formData.value.deviceIds = '' |
| | | if (deviceTableRef.value) { |
| | | deviceTableRef.value.clearSelection() |
| | | } |
| | | } |
| | | |
| | | function clearPoliceStationState () { |
| | | policeStationOptions.value = [] |
| | | formData.value.policeStationId = '' |
| | | } |
| | | |
| | | function formatWktNumber (value) { |
| | | if (!Number.isFinite(Number(value))) return null |
| | | return Number(value).toFixed(6) |
| | | } |
| | | |
| | | function isSameWktPoint (left, right) { |
| | | if (!left || !right) return false |
| | | return Math.abs(left.lat - right.lat) < 0.000001 && Math.abs(left.lng - right.lng) < 0.000001 |
| | | } |
| | | |
| | | function buildPolygonWktFromPoints (points) { |
| | | if (!Array.isArray(points) || points.length < 3) return null |
| | | const ring = points |
| | | .map(resolveLngLatPoint) |
| | | .filter(Boolean) |
| | | .map(point => ({ lng: point.lng, lat: point.lat })) |
| | | if (ring.length < 3) return null |
| | | const first = ring[0] |
| | | const last = ring[ring.length - 1] |
| | | if (!isSameWktPoint(first, last)) { |
| | | ring.push({ ...first }) |
| | | } |
| | | const coordText = ring |
| | | .map(point => { |
| | | const lng = formatWktNumber(point.lng) |
| | | const lat = formatWktNumber(point.lat) |
| | | if (lat === null || lng === null) return null |
| | | return `${lng} ${lat}` |
| | | }) |
| | | .filter(Boolean) |
| | | .join(', ') |
| | | if (!coordText) return null |
| | | return `POLYGON((${coordText}))` |
| | | } |
| | | |
| | | function buildEllipseWktFromMeta (center, semiMajor, semiMinor) { |
| | | if (!viewer) return null |
| | | const resolvedCenter = resolveLngLatPoint(center) |
| | | if (!resolvedCenter) return null |
| | | if (!Number.isFinite(Number(semiMajor)) || !Number.isFinite(Number(semiMinor))) return null |
| | | const centerCartesian = Cesium.Cartesian3.fromDegrees( |
| | | resolvedCenter.lng, |
| | | resolvedCenter.lat, |
| | | resolvedCenter.height || 0 |
| | | ) |
| | | const positions = buildEllipsePositions(centerCartesian, Number(semiMajor), Number(semiMinor)) |
| | | if (!positions.length) return null |
| | | const points = positions |
| | | .map(item => { |
| | | const converted = cartesian3Convert(item, viewer) |
| | | return { lng: converted.longitude, lat: converted.latitude } |
| | | }) |
| | | .filter(Boolean) |
| | | return buildPolygonWktFromPoints(points) |
| | | } |
| | | |
| | | function resolveShapePolygonsWkt () { |
| | | const polygons = [] |
| | | shapeList.value.forEach(shape => { |
| | | if (!shape) return |
| | | if (shape.drawType === 'buffer') { |
| | | const radii = Array.isArray(shape?.meta?.bufferRadii) |
| | | ? shape.meta.bufferRadii.filter(item => Number.isFinite(item) && item > 0) |
| | | : [] |
| | | const maxRadius = radii.length ? Math.max(...radii) : null |
| | | const wkt = buildEllipseWktFromMeta(shape?.meta?.center, maxRadius, maxRadius) |
| | | if (wkt) polygons.push(wkt) |
| | | return |
| | | } |
| | | if (shape.drawType === 'ellipse') { |
| | | const wkt = buildEllipseWktFromMeta(shape?.meta?.center, shape?.meta?.semiMajor, shape?.meta?.semiMinor) |
| | | if (wkt) { |
| | | polygons.push(wkt) |
| | | return |
| | | } |
| | | } |
| | | const points = getShapeDisplayPoints(shape) |
| | | const wkt = buildPolygonWktFromPoints(points) |
| | | if (wkt) polygons.push(wkt) |
| | | }) |
| | | return polygons |
| | | } |
| | | |
| | | async function renderDeviceRangeSingle (device) { |
| | |
| | | width: 2, |
| | | material: style.outline, |
| | | }, |
| | | }) |
| | | addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, { |
| | | name: 'shape-display', |
| | | customData: { drawType: 'buffer', label: 'radius' }, |
| | | }) |
| | | }) |
| | | } |
| | |
| | | } |
| | | |
| | | // 获取派出所列表 |
| | | async function getPoliceStationList () { |
| | | const res = await fwPoliceStationListApi() |
| | | function queuePoliceStationRefresh () { |
| | | if (!visible.value) return |
| | | if (policeStationListTimer) { |
| | | clearTimeout(policeStationListTimer) |
| | | } |
| | | policeStationListTimer = setTimeout(() => { |
| | | policeStationListTimer = null |
| | | void getPoliceStationListByPolygons() |
| | | }, 200) |
| | | } |
| | | |
| | | async function getPoliceStationListByPolygons () { |
| | | if (!visible.value) return |
| | | const polygons = resolveShapePolygonsWkt() |
| | | if (!polygons.length) { |
| | | clearPoliceStationState() |
| | | return |
| | | } |
| | | policeStationRequestToken += 1 |
| | | const requestToken = policeStationRequestToken |
| | | const res = await fwPoliceStationQueryByPolygonsApi({ polygons }) |
| | | if (requestToken !== policeStationRequestToken) return |
| | | policeStationOptions.value = res?.data?.data ?? [] |
| | | await nextTick() |
| | | const matched = policeStationOptions.value.find( |
| | | item => String(item.id) === String(formData.value.policeStationId) |
| | | ) |
| | | if (!matched) { |
| | | formData.value.policeStationId = '' |
| | | } |
| | | } |
| | | |
| | | // 获取设备列表 |
| | | async function getDeviceList () { |
| | | if (deviceOptions.value.length) return |
| | | const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 }) |
| | | function queueDeviceListRefresh () { |
| | | if (!visible.value) return |
| | | if (deviceListTimer) { |
| | | clearTimeout(deviceListTimer) |
| | | } |
| | | deviceListTimer = setTimeout(() => { |
| | | deviceListTimer = null |
| | | void getDeviceListByPolygons() |
| | | }, 200) |
| | | } |
| | | |
| | | async function getDeviceListByPolygons () { |
| | | if (!visible.value) return |
| | | const polygons = resolveShapePolygonsWkt() |
| | | if (!polygons.length) { |
| | | clearDeviceListState() |
| | | return |
| | | } |
| | | deviceListRequestToken += 1 |
| | | const requestToken = deviceListRequestToken |
| | | const res = await fwDeviceListByPolygonsApi({ |
| | | isAreaSelect: 1, |
| | | isTrack: 1, |
| | | polygons, |
| | | }) |
| | | if (requestToken !== deviceListRequestToken) return |
| | | deviceOptions.value = res?.data?.data ?? [] |
| | | if (dialogMode.value === 'view') { |
| | | deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id)) |
| | | if (dialogMode.value === 'view' && formData.value.deviceIds) { |
| | | const targetIds = formData.value.deviceIds.split(',') |
| | | deviceOptions.value = deviceOptions.value.filter(item => targetIds.includes(String(item.id))) |
| | | } |
| | | await nextTick() |
| | | const prevSuppressDeviceFly = suppressDeviceFly.value |
| | | suppressDeviceFly.value = true |
| | | syncDeviceSelection() |
| | | suppressDeviceFly.value = prevSuppressDeviceFly |
| | | if (!readonly.value) { |
| | | renderDeviceRanges(selectedDeviceRows.value) |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // 同步选择状态 |
| | | function syncDeviceSelection () { |
| | | if (isDialogInitializing.value && !deviceOptions.value.length) return |
| | | const rows = [] |
| | | const arr = formData.value.deviceIds.split(',') |
| | | const arr = formData.value.deviceIds ? formData.value.deviceIds.split(',') : [] |
| | | deviceOptions.value.forEach(row => { |
| | | if (arr.includes(row.id)) { |
| | | if (arr.includes(String(row.id))) { |
| | | rows.push(row) |
| | | } |
| | | }) |
| | | selectedDeviceRows.value = rows |
| | | formData.value.deviceIds = rows.map(item => item.id).join(',') |
| | | if (readonly.value) { |
| | | renderDeviceRanges(selectedDeviceRows.value) |
| | | return |
| | |
| | | |
| | | // 打开弹框 |
| | | async function open ({ mode, row } = {}) { |
| | | isDialogInitializing.value = true |
| | | dialogMode.value = mode || 'add' |
| | | formData.value = dialogMode.value === 'add' ? initForm() : row |
| | | selectedDeviceRows.value = [] |
| | | deviceOptions.value = [] |
| | | policeStationOptions.value = [] |
| | | shapeList.value = [] |
| | | activeShapeId.value = null |
| | | activeAreaType.value = '' |
| | |
| | | viewEntity && viewer?.entities?.remove(viewEntity) |
| | | viewEntity = null |
| | | clearActiveTool() |
| | | await getDeviceList() |
| | | if (dialogMode.value === 'add') { |
| | | mapRef.value?.zoomToAdminBoundary?.() |
| | | // default no draw mode |
| | | } else if (dialogMode.value === 'edit') { |
| | | await loadDetail() |
| | |
| | | if (!readonly.value) { |
| | | renderDeviceRanges(selectedDeviceRows.value) |
| | | } |
| | | isDialogInitializing.value = false |
| | | queueDeviceListRefresh() |
| | | queuePoliceStationRefresh() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getPoliceStationList() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (deviceListTimer) { |
| | | clearTimeout(deviceListTimer) |
| | | deviceListTimer = null |
| | | } |
| | | if (policeStationListTimer) { |
| | | clearTimeout(policeStationListTimer) |
| | | policeStationListTimer = null |
| | | } |
| | | hideTypePanel() |
| | | }) |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | // 多边形区域查询派出所 |
| | | export const fwPoliceStationQueryByPolygonsApi = data => { |
| | | return request({ |
| | | url: `/drone-fw/area/fwPoliceStation/queryByPolygons`, |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 增加或更新 |
| | | export const fwPoliceStationSubmitApi = data => { |
| | | return request({ |
| | |
| | | import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue' |
| | | import * as Cesium from 'cesium' |
| | | import { fwAreaDivideDetailApi, fwAreaDivideListApi } from '../partition/partitionApi' |
| | | import { buildEllipsePositions } from '@/utils/cesium/shapeTools' |
| | | import { |
| | | addBufferRadiusLabelEntity, |
| | | buildEllipsePositions, |
| | | } from '@/utils/cesium/shapeTools' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import { saveOperationLog } from '@ztzf/apis' |
| | | import { useRoute } from 'vue-router' |
| | |
| | | material: style.outline, |
| | | }, |
| | | }) |
| | | addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, { |
| | | name: 'scene-area-display', |
| | | customData: { drawType: 'buffer', label: 'radius' }, |
| | | }) |
| | | }) |
| | | return |
| | | } |
| | |
| | | initMap() |
| | | await getAreaList() |
| | | if (dialogMode.value === 'add') { |
| | | console.log(111) |
| | | |
| | | mapRef.value?.zoomToAdminBoundary?.() |
| | | } else { |
| | | await loadDetail() |
| | |
| | | import { saveOperationLog } from '@ztzf/apis' |
| | | import { useRoute } from 'vue-router' |
| | | import { fwAreaDivideDetailApi, fwAreaDivideListApi } from '../partition/partitionApi' |
| | | import { buildEllipsePositions } from '@/utils/cesium/shapeTools' |
| | | import { |
| | | addBufferRadiusLabelEntity, |
| | | buildEllipsePositions, |
| | | } from '@/utils/cesium/shapeTools' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants' |
| | | import { MapTooltip } from '@ztzf/utils' |
| | |
| | | material: style.outline, |
| | | }, |
| | | }) |
| | | addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, { |
| | | name: 'scene-manage-area-display', |
| | | customData: { drawType: 'buffer', label: 'radius' }, |
| | | }) |
| | | }) |
| | | return |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | // 多边形过滤设备列表 |
| | | export const fwDeviceListByPolygonsApi = data => { |
| | | return request({ |
| | | url: `/drone-fw/device/fwDevice/listByPolygons`, |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 增加或更新 |
| | | export const fwDeviceSubmitApi = data => { |
| | | return request({ |
| | |
| | | |
| | | # 预览地址 previewURL |
| | | VITE_APP_PREVIEW_URL=http://192.168.1.204:8012 |
| | | |
| | | VITE_APP_TERRAIN_URL=https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_c_uas/ |
| | |
| | | # 航线文件地址 |
| | | VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket |
| | | |
| | | # 图片存放地址 |
| | | VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/ |
| | | # 行政区划存放地址 |
| | | VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region |
| | | # 算法仓库图片地址 |
| | |
| | | // 业务组件 |
| | | import tenantPackage from './views/system/tenantpackage.vue' |
| | | // 地图依赖 |
| | | import mapContainer from './components/map-container/mapContainer.vue' |
| | | |
| | | import * as DC from '@dvgis/dc-sdk' |
| | | import '@dvgis/dc-sdk/dist/dc.min.css' |
| | |
| | | app.component('thirdRegister', thirdRegister) |
| | | app.component('flowDesign', flowDesign) |
| | | app.component('tenantPackage', tenantPackage) |
| | | app.component('mapContainer', mapContainer) |
| | | |
| | | app.config.globalProperties.$dayjs = dayjs |
| | | app.config.globalProperties.website = website |
| | |
| | | }, |
| | | ], |
| | | }, |
| | | |
| | | { |
| | | path: '/resource', |
| | | component: Layout, |
| | | redirect: '/resource/patchManagement', |
| | | children: [ |
| | | { |
| | | path: 'patchManagement', |
| | | name: '图斑管理', |
| | | meta: { |
| | | i18n: 'info', |
| | | }, |
| | | component: () => import(/* webpackChunkName: "views" */ '@/views/resource/patchManagement.vue'), |
| | | }, |
| | | |
| | | { |
| | | path: 'patchTypeManagement', |
| | | name: '图斑类型管理', |
| | | meta: { |
| | | i18n: 'info', |
| | | }, |
| | | component: () => import(/* webpackChunkName: "views" */ '@/views/resource/patchTypeManagement.vue'), |
| | | }, |
| | | ], |
| | | }, |
| | | ] |
| | |
| | | |
| | | this.viewer?.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 禁用双击 |
| | | |
| | | if (terrain) { |
| | | try { |
| | | const noTerrainMechanism = ['1962779164650135554'] |
| | | if (noTerrainMechanism.includes(store.state.user.userInfo?.deptId)) { |
| | | // 使用Cesium World Terrain |
| | | Cesium.createWorldTerrainAsync({ |
| | | requestWaterMask: false, // 请求水域效果 |
| | | requestVertexNormals: true // 请求光照和地形法线 |
| | | }).then(terrainProvider => { |
| | | this.viewer.terrainProvider = terrainProvider |
| | | terrainLoadCallback?.() |
| | | }) |
| | | } else { |
| | | /*// 正则:第一位非0,第二位任意数字,后10位都是0 |
| | | const re = /^[1-9]\d0{10}$/ |
| | | |
| | | let result = null |
| | | |
| | | // 1️⃣先判断 areaCode |
| | | if (re.test(store.state.user.userInfo.detail.areaCode)) { |
| | | result = store.state.user.userInfo.detail.areaCode.slice(0, 6) |
| | | } else { |
| | | // 2️⃣如果不满足,从 ancestors 里找 |
| | | const matchedItems = store.state.user.userInfo.detail.ancestors |
| | | .split(',') |
| | | .filter(item => re.test(item)) // 过滤出符合条件的 |
| | | |
| | | // 取第一个符合条件的前6位(可以改成 last one 看需求) |
| | | if (matchedItems.length > 0) { |
| | | result = matchedItems[0].slice(0, 6) |
| | | } |
| | | } |
| | | |
| | | // 使用公司地形 |
| | | Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}${result}`, { |
| | | requestVertexNormals: true, // 启用地形法线增强立体感 |
| | | requestWaterMask: true, // 启用水体遮罩效果 |
| | | }).then(terrainProvider => { |
| | | this.viewer.terrainProvider = terrainProvider |
| | | terrainLoadCallback?.() |
| | | })*/ |
| | | } |
| | | } catch (error) { |
| | | console.error('地形加载失败:', error) |
| | | } |
| | | } |
| | | this.setTerrainVisible(terrain, terrainLoadCallback) |
| | | |
| | | this.viewer.scene.screenSpaceCameraController.maximumZoomDistance = 4500000 |
| | | this.switchLayers(layerMode) |
| | |
| | | this.viewer.resolutionScale = dpr // 设置分辨率缩放比例 |
| | | } |
| | | } |
| | | async ensureTerrainProvider () { |
| | | if (this.terrainProvider) return this.terrainProvider |
| | | if (this.terrainLoading) return this.terrainLoading |
| | | this.terrainLoading = Cesium.CesiumTerrainProvider.fromUrl( |
| | | `${import.meta.env.VITE_APP_TERRAIN_URL}ja_terrain`, |
| | | { |
| | | requestVertexNormals: true, // 启用地形法线增强立体感 |
| | | requestWaterMask: true, // 启用水体遮罩效果 |
| | | } |
| | | ).then(provider => { |
| | | this.terrainProvider = provider |
| | | return provider |
| | | }) |
| | | return this.terrainLoading |
| | | } |
| | | |
| | | async setTerrainVisible (visible, terrainLoadCallback) { |
| | | if (!this.viewer) return |
| | | if (!visible) { |
| | | this.viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider() |
| | | this.terrainEnabled = false |
| | | return |
| | | } |
| | | try { |
| | | const provider = await this.ensureTerrainProvider() |
| | | if (!this.viewer) return |
| | | this.viewer.terrainProvider = provider |
| | | this.terrainEnabled = true |
| | | terrainLoadCallback?.() |
| | | } catch (error) { |
| | | this.terrainEnabled = false |
| | | } |
| | | } |
| | | |
| | | getViewer () { |
| | | return this.viewer |
| New file |
| | |
| | | <template> |
| | | <div> |
| | | 实施清单 |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup > |
| | | /** |
| | | * @description index |
| | | * @date 2026-02-02 (周一) 09:25:52 |
| | | */ |
| | | defineOptions({ |
| | | name: 'index' |
| | | }) |
| | | |
| | | |
| | | </script> |
| | | |
| | | <style scoped > |
| | | </style> |
| New file |
| | |
| | | |
| | | |
| | | ## 分页 |
| | | |
| | | |
| | | **接口地址**:`/implement/gdImplementList/page` |
| | | |
| | | |
| | | **请求方式**:`GET` |
| | | |
| | | |
| | | **请求数据类型**:`application/x-www-form-urlencoded` |
| | | |
| | | |
| | | **响应数据类型**:`*/*` |
| | | |
| | | |
| | | **接口描述**:<p>传入gdImplementList</p> |
| | | |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema | |
| | | | -------- | -------- | ----- | -------- | -------- | ------ | |
| | | |areaCode|所属区划编码|query|false|string|| |
| | | |current|当前页|query|false|integer(int32)|| |
| | | |matterCode|事项编码|query|false|string|| |
| | | |matterName|事项名称|query|false|string|| |
| | | |size|每页的数量|query|false|integer(int32)|| |
| | | |
| | | |
| | | **响应状态**: |
| | | |
| | | |
| | | | 状态码 | 说明 | schema | |
| | | | -------- | -------- | ----- | |
| | | |200|OK|R«IPage«GdImplementListVO»»| |
| | | |401|Unauthorized|| |
| | | |403|Forbidden|| |
| | | |404|Not Found|| |
| | | |
| | | |
| | | **响应参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 类型 | schema | |
| | | | -------- | -------- | ----- |----- | |
| | | |code|状态码|integer(int32)|integer(int32)| |
| | | |data|承载数据|IPage«GdImplementListVO»|IPage«GdImplementListVO»| |
| | | |  current||integer(int64)|| |
| | | |  pages||integer(int64)|| |
| | | |  records||array|GdImplementListVO| |
| | | |    areaCode|所属区划编码|string|| |
| | | |    areaName|所属区划名称|string|| |
| | | |    catalogCode|目录编码|string|| |
| | | |    id|主键id|integer|| |
| | | |    implementCode|实施编码|string|| |
| | | |    implementName|实施清单名称|string|| |
| | | |    matterCode|事项编码|string|| |
| | | |    matterName|事项名称|string|| |
| | | |    orgCode|所属机构编码|string|| |
| | | |    orgName|所属机构名称|string|| |
| | | |    processDefinitionId|流程定义主键|string|| |
| | | |    processInstanceId|流程实例主键|string|| |
| | | |  size||integer(int64)|| |
| | | |  total||integer(int64)|| |
| | | |msg|返回消息|string|| |
| | | |success|是否成功|boolean|| |
| | | |
| | | |
| | | **响应示例**: |
| | | ```javascript |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "current": 0, |
| | | "pages": 0, |
| | | "records": [ |
| | | { |
| | | "areaCode": "", |
| | | "areaName": "", |
| | | "catalogCode": "", |
| | | "id": 0, |
| | | "implementCode": "", |
| | | "implementName": "", |
| | | "matterCode": "", |
| | | "matterName": "", |
| | | "orgCode": "", |
| | | "orgName": "", |
| | | "processDefinitionId": "", |
| | | "processInstanceId": "" |
| | | } |
| | | ], |
| | | "size": 0, |
| | | "total": 0 |
| | | }, |
| | | "msg": "", |
| | | "success": true |
| | | } |
| | | ``` |
| New file |
| | |
| | | <template> |
| | | <el-dialog |
| | | class="gd-dialog" |
| | | v-model="visible" |
| | | :title="titleEnum[dialogMode]" |
| | | @closed="visible = false" |
| | | destroy-on-close |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-row class="detail-row-view" v-if="dialogReadonly"> |
| | | <el-col :span="12"> |
| | | <div class="label">材料名称</div> |
| | | <div class="val">{{ formData.materialName }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">材料说明</div> |
| | | <div class="val">{{ formData.materialDesc }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">必要性</div> |
| | | <div class="val">{{ getDictLabel(formData.necessity, dictObj.necessity) }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">是否适用全部情形</div> |
| | | <div class="val">{{ getDictLabel(String(formData.isAllScenario), dictObj.isAllScenario) }}</div> |
| | | </el-col> |
| | | <!-- <el-col :span="12"> |
| | | <div class="label">所属区域</div> |
| | | <div class="val">{{ formData.areaName }}</div> |
| | | </el-col> --> |
| | | </el-row> |
| | | |
| | | <el-form |
| | | class="gd-dialog-form" |
| | | v-else |
| | | ref="formRef" |
| | | :model="formData" |
| | | :rules="rules" |
| | | :disabled="dialogReadonly" |
| | | label-width="140px" |
| | | > |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="材料名称" prop="materialName"> |
| | | <el-input class="gd-input" v-model="formData.materialName" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="材料说明" prop="materialDesc"> |
| | | <el-input class="gd-input" v-model="formData.materialDesc" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="必要性" prop="necessity"> |
| | | <el-select |
| | | class="gd-select" |
| | | popper-class="gd-select-popper" |
| | | v-model="formData.necessity" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in dictObj.necessity" |
| | | :key="item.dictKey" |
| | | :label="item.dictValue" |
| | | :value="item.dictKey" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="是否适用全部情形" prop="isAllScenario"> |
| | | <el-select |
| | | class="gd-select" |
| | | popper-class="gd-select-popper" |
| | | v-model="formData.isAllScenario" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in dictObj.isAllScenario" |
| | | :key="item.dictKey" |
| | | :label="item.dictValue" |
| | | :value="Number(item.dictKey)" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="所属区域" prop="areaCode"> |
| | | <el-tree-select |
| | | class="gd-select" |
| | | popper-class="gd-tree-select-popper" |
| | | v-model="formData.areaCode" |
| | | node-key="id" |
| | | :default-expanded-keys="expandedKeys" |
| | | :props="treeSelectProps" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | :check-strictly="true" |
| | | lazy |
| | | :load="loadRegionNode" |
| | | :render-after-expand="false" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> --> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer v-if="!dialogReadonly"> |
| | | <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button> |
| | | <el-button class="save-btn" color="#4C34FF" :loading="submitting" :disabled="submitting" @click="handleSubmit"> |
| | | 保存 |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getDictLabel } from '@ztzf/utils' |
| | | import { computed, ref, inject } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { fieldRules } from '@ztzf/utils' |
| | | import { materialsSubmitApi, materialsDetailApi, regionLazyTreeApi } from './materialsApi' |
| | | |
| | | // 初始化表单数据 |
| | | const initForm = () => ({ |
| | | materialName: '', |
| | | materialDesc: '', |
| | | necessity: '', |
| | | isAllScenario: '', |
| | | areaCode: '', |
| | | id: null, |
| | | }) |
| | | |
| | | const dictObj = inject('dictObj') |
| | | |
| | | const emit = defineEmits(['success']) |
| | | const formRef = ref(null) // 表单实例 |
| | | const formData = ref(initForm()) // 表单数据 |
| | | const visible = defineModel() // 弹框显隐 |
| | | const dialogMode = ref('add') // 弹框模式 |
| | | const submitting = ref(false) // 提交中 |
| | | const dialogReadonly = computed(() => dialogMode.value === 'view') |
| | | const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' }) |
| | | const expandedKeys = ref([]) // 树形选择器展开的节点 |
| | | const regionNodeCache = ref(new Map()) // 缓存区域节点及其父节点关系 |
| | | |
| | | // 树形选择器配置 |
| | | const treeSelectProps = ref({ |
| | | value: 'id', |
| | | label: 'name', |
| | | children: 'children', |
| | | // 判断叶子节点 |
| | | isLeaf: (data, node) => { |
| | | return node.level >= 2 |
| | | }, |
| | | }) |
| | | |
| | | // 表单校验规则 |
| | | const rules = { |
| | | materialName: fieldRules(true), |
| | | materialDesc: fieldRules(true), |
| | | necessity: fieldRules(true), |
| | | isAllScenario: fieldRules(true), |
| | | areaCode: fieldRules(true), |
| | | } |
| | | |
| | | // 提交新增/编辑 |
| | | async function handleSubmit() { |
| | | const isValid = await formRef.value?.validate().catch(() => false) |
| | | if (!isValid) return |
| | | submitting.value = true |
| | | try { |
| | | await materialsSubmitApi(formData.value) |
| | | ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功') |
| | | visible.value = false |
| | | emit('success') |
| | | } finally { |
| | | submitting.value = false |
| | | } |
| | | } |
| | | |
| | | // 加载详情 |
| | | async function loadDetail() { |
| | | if (!formData.value.id) return |
| | | const res = await materialsDetailApi({ id: formData.value.id }) |
| | | const data = res?.data?.data ?? {} |
| | | // 将 areaCode 转换为字符串类型 |
| | | if (data.areaCode !== undefined) { |
| | | data.areaCode = String(data.areaCode) |
| | | } |
| | | formData.value = data |
| | | |
| | | // 如果是编辑模式,获取区域节点的路径以展开树形选择器 |
| | | if (dialogMode.value === 'edit' && data.areaCode) { |
| | | await loadRegionPath(data.areaCode) |
| | | } |
| | | } |
| | | |
| | | // 加载区域节点路径 |
| | | async function loadRegionPath(areaCode) { |
| | | try { |
| | | const path = [] |
| | | const queue = [{ code: '360800000000', currentPath: ['360800000000'] }] |
| | | let found = false |
| | | |
| | | // 广度优先搜索,逐层加载节点直到找到目标节点 |
| | | while (queue.length > 0 && !found) { |
| | | const { code, currentPath } = queue.shift() |
| | | |
| | | // 获取当前节点的子节点 |
| | | const res = await regionLazyTreeApi({ parentCode: code }) |
| | | const nodes = res?.data?.data || [] |
| | | |
| | | for (const node of nodes) { |
| | | const nodeId = node.id || node.value |
| | | |
| | | // 缓存节点及其父节点关系 |
| | | regionNodeCache.value.set(nodeId, { |
| | | id: nodeId, |
| | | name: node.name || node.title || node.label, |
| | | parentId: code, |
| | | hasChildren: node.hasChildren || false, |
| | | }) |
| | | |
| | | // 检查是否找到目标节点 |
| | | if (nodeId === areaCode) { |
| | | path.push(...currentPath, nodeId) |
| | | found = true |
| | | break |
| | | } |
| | | |
| | | // 如果有子节点,加入队列继续搜索 |
| | | if (node.hasChildren) { |
| | | queue.push({ |
| | | code: nodeId, |
| | | currentPath: [...currentPath, nodeId], |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (path.length > 0) { |
| | | expandedKeys.value = path |
| | | } |
| | | } catch (error) { |
| | | console.error('加载区域路径失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 打开弹框 |
| | | async function open({ mode = 'add', row } = {}) { |
| | | dialogMode.value = mode |
| | | formData.value = dialogMode.value === 'add' ? initForm() : row |
| | | if (dialogMode.value !== 'add') { |
| | | await loadDetail() |
| | | } |
| | | } |
| | | |
| | | // 懒加载获取区域节点数据 |
| | | async function loadRegionNode(node, resolve) { |
| | | try { |
| | | // 限制只加载两级,当前节点 level >= 1 时不加载子节点 |
| | | if (node.level >= 2) { |
| | | resolve([]) |
| | | return |
| | | } |
| | | // 初始化加载时传递 code 参数,加载子节点时传递 parentCode 参数 |
| | | const params = node.data?.id ? { parentCode: node.data.id } : { code: '360800000000' } |
| | | const parentCode = node.data?.id || '360800000000' |
| | | const res = await regionLazyTreeApi(params) |
| | | const nodes = res?.data?.data || [] |
| | | |
| | | // 处理返回的节点数据 |
| | | const processedNodes = nodes.map(item => { |
| | | const nodeId = item.id || item.value |
| | | // 缓存节点及其父节点关系 |
| | | regionNodeCache.value.set(nodeId, { |
| | | id: nodeId, |
| | | name: item.name || item.title || item.label, |
| | | parentId: parentCode, |
| | | hasChildren: item.hasChildren || false, |
| | | }) |
| | | |
| | | return { |
| | | id: nodeId, |
| | | name: item.name || item.title || item.label, |
| | | hasChildren: item.hasChildren || false, |
| | | } |
| | | }) |
| | | |
| | | resolve(processedNodes) |
| | | } catch (error) { |
| | | console.error('获取区域数据失败:', error) |
| | | resolve([]) |
| | | } |
| | | } |
| | | |
| | | defineExpose({ open }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| New file |
| | |
| | | <template> |
| | | <basic-container> |
| | | <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form"> |
| | | <el-form-item label="材料名称" prop="materialName"> |
| | | <el-input |
| | | class="gd-input gray" |
| | | v-model="searchParams.materialName" |
| | | placeholder="请输入" |
| | | clearable |
| | | @clear="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <!-- <el-form-item label="所属区划" prop="areaCode"> |
| | | <el-tree-select |
| | | class="gd-select gray" |
| | | popper-class="gd-tree-select-popper" |
| | | v-model="searchParams.areaCode" |
| | | node-key="id" |
| | | :props="treeSelectProps" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | @change="handleSearch" |
| | | :check-strictly="true" |
| | | lazy |
| | | :load="loadRegionNode" |
| | | :render-after-expand="false" |
| | | /> |
| | | </el-form-item> --> |
| | | |
| | | <el-form-item class="gd-search-actions"> |
| | | <el-button :icon="RefreshRight" @click="resetForm"></el-button> |
| | | <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="gd-table-toolbar"> |
| | | <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增材料</el-button> |
| | | </div> |
| | | |
| | | <div class="gd-table-container" v-loading="loading"> |
| | | <div class="gd-table-content gd-table-content-bg"> |
| | | <el-table class="gd-table" :data="list"> |
| | | <el-table-column label="序号" width="80"> |
| | | <template v-slot="{ $index }"> |
| | | {{ $index + 1 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="materialName" show-overflow-tooltip label="材料名称" /> |
| | | <el-table-column prop="materialDesc" show-overflow-tooltip label="材料说明" /> |
| | | <el-table-column prop="necessity" show-overflow-tooltip label="必要性"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.necessity, dictObj.necessity) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="isAllScenario" show-overflow-tooltip label="是否适用全部情形"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(String(row.isAllScenario), dictObj.isAllScenario) }} |
| | | </template> |
| | | </el-table-column> |
| | | <!-- <el-table-column prop="areaName" show-overflow-tooltip label="所属区域" /> --> |
| | | <el-table-column label="操作" class-name="operation-btns" width="200"> |
| | | <template v-slot="{ row }"> |
| | | <el-link type="primary" @click="openForm('view', row)">查看</el-link> |
| | | <el-link type="primary" @click="openForm('edit', row)">编辑</el-link> |
| | | <el-link type="primary" @click="handleDelete(row)">删除</el-link> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="gd-pagination-parent"> |
| | | <el-pagination |
| | | popper-class="gd-select-popper" |
| | | v-model:current-page="searchParams.current" |
| | | v-model:page-size="searchParams.size" |
| | | layout="total, prev, pager, next, sizes" |
| | | :total="total" |
| | | @change="getList" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" /> |
| | | </basic-container> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search, RefreshRight, Plus } from '@element-plus/icons-vue' |
| | | import { onMounted, ref, provide, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import FormDiaLog from './FormDiaLog.vue' |
| | | import { materialsPageApi, materialsRemoveApi, regionLazyTreeApi } from './materialsApi' |
| | | import { getDictLabel } from '@ztzf/utils' |
| | | |
| | | // 初始化查询参数 |
| | | const initSearchParams = () => ({ |
| | | materialName: '', // 材料名称 |
| | | areaCode: '', // 所属区划 |
| | | current: 1, // 当前页 |
| | | size: 10, // 每页大小 |
| | | }) |
| | | const searchParams = ref(initSearchParams()) // 查询参数 |
| | | const total = ref(0) // 总条数 |
| | | const loading = ref(true) // 列表加载中 |
| | | const list = ref([]) // 列表数据 |
| | | const queryParamsRef = ref(null) // 查询表单实例 |
| | | const dialogRef = ref(null) // 弹框实例 |
| | | const dialogVisible = ref(false) |
| | | const dictObj = ref({ |
| | | necessity: [], |
| | | isAllScenario: [], |
| | | }) // 字典对象 |
| | | |
| | | // 树形选择器配置 |
| | | const treeSelectProps = { |
| | | value: 'id', |
| | | label: 'name', |
| | | children: 'children', |
| | | // 判断叶子节点 |
| | | isLeaf: (data, node) => { |
| | | return data.leaf === true |
| | | }, |
| | | } |
| | | |
| | | provide('dictObj', dictObj) |
| | | |
| | | // 获取字典列表 |
| | | function getDictList() { |
| | | // 必要性字典:必填/可选/容缺 |
| | | dictObj.value.necessity = [ |
| | | { dictKey: '必填', dictValue: '必填' }, |
| | | { dictKey: '可选', dictValue: '可选' }, |
| | | { dictKey: '容缺', dictValue: '容缺' }, |
| | | ] |
| | | // 是否适用全部情形字典 |
| | | dictObj.value.isAllScenario = [ |
| | | { dictKey: '1', dictValue: '是' }, |
| | | { dictKey: '0', dictValue: '否' }, |
| | | ] |
| | | } |
| | | |
| | | // 懒加载获取区域节点数据 |
| | | async function loadRegionNode(node, resolve) { |
| | | try { |
| | | // 限制只加载两级,当前节点 level >= 1 时不加载子节点 |
| | | if (node.level >= 2) { |
| | | resolve([]) |
| | | return |
| | | } |
| | | |
| | | // 初始化加载时传递 code 参数,加载子节点时传递 parentCode 参数 |
| | | const params = node.data?.id ? { parentCode: node.data.id } : { code: '360800000000' } |
| | | const res = await regionLazyTreeApi(params) |
| | | const nodes = res?.data?.data || [] |
| | | |
| | | // 处理返回的节点数据 |
| | | const processedNodes = nodes.map(item => { |
| | | // 判断是否为叶子节点:如果是第二级(node.level === 1)则为叶子节点 |
| | | const isLeaf = node.level === 1 |
| | | |
| | | return { |
| | | id: item.id || item.value, |
| | | name: item.name || item.title || item.label, |
| | | hasChildren: item.hasChildren || false, |
| | | leaf: isLeaf, |
| | | } |
| | | }) |
| | | |
| | | resolve(processedNodes) |
| | | } catch (error) { |
| | | console.error('获取区域数据失败:', error) |
| | | resolve([]) |
| | | } |
| | | } |
| | | |
| | | // 获取列表 |
| | | async function getList() { |
| | | loading.value = true |
| | | try { |
| | | const res = await materialsPageApi(searchParams.value) |
| | | list.value = res?.data?.data?.records ?? [] |
| | | total.value = res?.data?.data?.total ?? 0 |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // 查询 |
| | | function handleSearch() { |
| | | searchParams.value.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // 重置查询 |
| | | function resetForm() { |
| | | queryParamsRef.value?.resetFields() |
| | | searchParams.value.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // 新增/编辑/查看 弹框 |
| | | function openForm(mode, row) { |
| | | dialogVisible.value = true |
| | | nextTick(() => { |
| | | dialogRef.value?.open({ mode, row }) |
| | | }) |
| | | } |
| | | |
| | | // 删除 |
| | | async function handleDelete(row) { |
| | | await ElMessageBox.confirm('确认删除该条记录吗?', '提示', { |
| | | type: 'warning', |
| | | customClass: 'gd-confirm-custom', |
| | | confirmButtonClass: 'gd-confirm-button', |
| | | cancelButtonClass: 'gd-confirm-cancel-button', |
| | | }) |
| | | await materialsRemoveApi({ ids: row.id }) |
| | | ElMessage.success('删除成功') |
| | | getList() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDictList() |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| New file |
| | |
| | | import request from '@/axios' |
| | | |
| | | // 分页查询材料列表 |
| | | export const materialsPageApi = params => { |
| | | return request({ |
| | | url: `/drone-gd/implement/gdMaterial/page`, |
| | | method: 'get', |
| | | params: { ...params }, |
| | | }) |
| | | } |
| | | |
| | | // 获取材料详情 |
| | | export const materialsDetailApi = params => { |
| | | return request({ |
| | | url: `/drone-gd/implement/gdMaterial/detail`, |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // 新增或编辑材料 |
| | | export const materialsSubmitApi = data => { |
| | | return request({ |
| | | url: `/drone-gd/implement/gdMaterial/submit`, |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 删除材料 |
| | | export const materialsRemoveApi = params => { |
| | | return request({ |
| | | url: `/drone-gd/implement/gdMaterial/remove`, |
| | | method: 'post', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // 懒加载区域树 |
| | | export const regionLazyTreeApi = params => { |
| | | return request({ |
| | | url: `/blade-system/region/lazy-tree`, |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| New file |
| | |
| | | |
| | | |
| | | ## 分页 |
| | | |
| | | |
| | | **接口地址**:`/implement/gdMaterial/page` |
| | | |
| | | |
| | | **请求方式**:`GET` |
| | | |
| | | |
| | | **请求数据类型**:`application/x-www-form-urlencoded` |
| | | |
| | | |
| | | **响应数据类型**:`*/*` |
| | | |
| | | |
| | | **接口描述**:<p>传入gdMaterial</p> |
| | | |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema | |
| | | | -------- | -------- | ----- | -------- | -------- | ------ | |
| | | |areaCode|区域编码|query|false|string|| |
| | | |current|当前页|query|false|integer(int32)|| |
| | | |id|主键id|query|false|integer(int64)|| |
| | | |isAllScenario|是否适用全部情形:1是 0否|query|false|integer(int32)|| |
| | | |materialDesc|材料说明|query|false|string|| |
| | | |materialName|材料名称(列表展示字段)|query|false|string|| |
| | | |necessity|必要性:必填/可选/容缺|query|false|string|| |
| | | |size|每页的数量|query|false|integer(int32)|| |
| | | |
| | | |
| | | **响应状态**: |
| | | |
| | | |
| | | | 状态码 | 说明 | schema | |
| | | | -------- | -------- | ----- | |
| | | |200|OK|R«IPage«GdMaterialVO»»| |
| | | |401|Unauthorized|| |
| | | |403|Forbidden|| |
| | | |404|Not Found|| |
| | | |
| | | |
| | | **响应参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 类型 | schema | |
| | | | -------- | -------- | ----- |----- | |
| | | |code|状态码|integer(int32)|integer(int32)| |
| | | |data|承载数据|IPage«GdMaterialVO»|IPage«GdMaterialVO»| |
| | | |  current||integer(int64)|| |
| | | |  pages||integer(int64)|| |
| | | |  records||array|GdMaterialVO| |
| | | |    areaCode|区域编码|string|| |
| | | |    id|主键id|integer|| |
| | | |    isAllScenario|是否适用全部情形:1是 0否|integer|| |
| | | |    materialDesc|材料说明|string|| |
| | | |    materialName|材料名称(列表展示字段)|string|| |
| | | |    necessity|必要性:必填/可选/容缺|string|| |
| | | |  size||integer(int64)|| |
| | | |  total||integer(int64)|| |
| | | |msg|返回消息|string|| |
| | | |success|是否成功|boolean|| |
| | | |
| | | |
| | | **响应示例**: |
| | | ```javascript |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "current": 0, |
| | | "pages": 0, |
| | | "records": [ |
| | | { |
| | | "areaCode": "", |
| | | "id": 0, |
| | | "isAllScenario": 0, |
| | | "materialDesc": "", |
| | | "materialName": "", |
| | | "necessity": "" |
| | | } |
| | | ], |
| | | "size": 0, |
| | | "total": 0 |
| | | }, |
| | | "msg": "", |
| | | "success": true |
| | | } |
| | | ``` |
| | | |
| | | |
| | | ## 详情 |
| | | |
| | | |
| | | **接口地址**:`/implement/gdMaterial/detail` |
| | | |
| | | |
| | | **请求方式**:`GET` |
| | | |
| | | |
| | | **请求数据类型**:`application/x-www-form-urlencoded` |
| | | |
| | | |
| | | **响应数据类型**:`*/*` |
| | | |
| | | |
| | | **接口描述**:<p>传入gdMaterial</p> |
| | | |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema | |
| | | | -------- | -------- | ----- | -------- | -------- | ------ | |
| | | |id|主键|query|true|integer(int64)|| |
| | | |
| | | |
| | | **响应状态**: |
| | | |
| | | |
| | | | 状态码 | 说明 | schema | |
| | | | -------- | -------- | ----- | |
| | | |200|OK|R«GdMaterialVO»| |
| | | |401|Unauthorized|| |
| | | |403|Forbidden|| |
| | | |404|Not Found|| |
| | | |
| | | |
| | | **响应参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 类型 | schema | |
| | | | -------- | -------- | ----- |----- | |
| | | |code|状态码|integer(int32)|integer(int32)| |
| | | |data|承载数据|GdMaterialVO|GdMaterialVO| |
| | | |  areaCode|区域编码|string|| |
| | | |  id|主键id|integer(int64)|| |
| | | |  isAllScenario|是否适用全部情形:1是 0否|integer(int32)|| |
| | | |  materialDesc|材料说明|string|| |
| | | |  materialName|材料名称(列表展示字段)|string|| |
| | | |  necessity|必要性:必填/可选/容缺|string|| |
| | | |msg|返回消息|string|| |
| | | |success|是否成功|boolean|| |
| | | |
| | | |
| | | **响应示例**: |
| | | ```javascript |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "areaCode": "", |
| | | "id": 0, |
| | | "isAllScenario": 0, |
| | | "materialDesc": "", |
| | | "materialName": "", |
| | | "necessity": "" |
| | | }, |
| | | "msg": "", |
| | | "success": true |
| | | } |
| | | ``` |
| | | |
| | | |
| | | ## 新增或修改 |
| | | |
| | | |
| | | **接口地址**:`/implement/gdMaterial/submit` |
| | | |
| | | |
| | | **请求方式**:`POST` |
| | | |
| | | |
| | | **请求数据类型**:`application/json` |
| | | |
| | | |
| | | **响应数据类型**:`*/*` |
| | | |
| | | |
| | | **接口描述**:<p>传入gdMaterial</p> |
| | | |
| | | |
| | | |
| | | **请求示例**: |
| | | |
| | | |
| | | ```javascript |
| | | { |
| | | "areaCode": "", |
| | | "id": 0, |
| | | "isAllScenario": 0, |
| | | "materialDesc": "", |
| | | "materialName": "", |
| | | "necessity": "" |
| | | } |
| | | ``` |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema | |
| | | | -------- | -------- | ----- | -------- | -------- | ------ | |
| | | |gdMaterial|gdMaterial|body|true|GdMaterialDTO|GdMaterialDTO| |
| | | |  areaCode|区域编码||false|string|| |
| | | |  id|主键id||false|integer(int64)|| |
| | | |  isAllScenario|是否适用全部情形:1是 0否||false|integer(int32)|| |
| | | |  materialDesc|材料说明||false|string|| |
| | | |  materialName|材料名称(列表展示字段)||false|string|| |
| | | |  necessity|必要性:必填/可选/容缺||false|string|| |
| | | |
| | | |
| | | **响应状态**: |
| | | |
| | | |
| | | | 状态码 | 说明 | schema | |
| | | | -------- | -------- | ----- | |
| | | |200|OK|R| |
| | | |201|Created|| |
| | | |401|Unauthorized|| |
| | | |403|Forbidden|| |
| | | |404|Not Found|| |
| | | |
| | | |
| | | **响应参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 类型 | schema | |
| | | | -------- | -------- | ----- |----- | |
| | | |code|状态码|integer(int32)|integer(int32)| |
| | | |data|承载数据|object|| |
| | | |msg|返回消息|string|| |
| | | |success|是否成功|boolean|| |
| | | |
| | | |
| | | **响应示例**: |
| | | ```javascript |
| | | { |
| | | "code": 0, |
| | | "data": {}, |
| | | "msg": "", |
| | | "success": true |
| | | } |
| | | ``` |
| | | |
| | | |
| | | ## 逻辑删除 |
| | | |
| | | |
| | | **接口地址**:`/implement/gdMaterial/remove` |
| | | |
| | | |
| | | **请求方式**:`POST` |
| | | |
| | | |
| | | **请求数据类型**:`application/json` |
| | | |
| | | |
| | | **响应数据类型**:`*/*` |
| | | |
| | | |
| | | **接口描述**:<p>传入ids</p> |
| | | |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | **请求参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema | |
| | | | -------- | -------- | ----- | -------- | -------- | ------ | |
| | | |ids|主键集合|query|true|string|| |
| | | |
| | | |
| | | **响应状态**: |
| | | |
| | | |
| | | | 状态码 | 说明 | schema | |
| | | | -------- | -------- | ----- | |
| | | |200|OK|R| |
| | | |201|Created|| |
| | | |401|Unauthorized|| |
| | | |403|Forbidden|| |
| | | |404|Not Found|| |
| | | |
| | | |
| | | **响应参数**: |
| | | |
| | | |
| | | | 参数名称 | 参数说明 | 类型 | schema | |
| | | | -------- | -------- | ----- |----- | |
| | | |code|状态码|integer(int32)|integer(int32)| |
| | | |data|承载数据|object|| |
| | | |msg|返回消息|string|| |
| | | |success|是否成功|boolean|| |
| | | |
| | | |
| | | **响应示例**: |
| | | ```javascript |
| | | { |
| | | "code": 0, |
| | | "data": {}, |
| | | "msg": "", |
| | | "success": true |
| | | } |
| | | ``` |
| | |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="detail-container detail-container1" v-if="dialogReadonly"> |
| | | <div class="detail-title">异议详情</div> |
| | | <el-row class="detail-row-view"> |
| | | <el-col :span="12"> |
| | | <div class="label">标题</div> |
| | |
| | | }) |
| | | } |
| | | |
| | | export const getDeptTreeListApi = tenantId => { |
| | | return request({ |
| | | url: '/blade-system/dept/tree', |
| | | method: 'get', |
| | | params: { |
| | | tenantId, |
| | | }, |
| | | }); |
| | | }; |
| | |
| | | import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue' |
| | | import { onMounted, ref, provide, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { getDeptTree } from '@/api/system/dept' |
| | | import { getDeptTreeListApi } from './dataObjectionApi' |
| | | import { getDictLabel } from '@ztzf/utils' |
| | | import FormDiaLog from './FormDiaLog.vue' |
| | | import { |
| | |
| | | const deptTree = ref([]) // 部门树 |
| | | const dictObj = ref({}) // 字典对象 |
| | | const treeProps = { |
| | | label: 'name', |
| | | label: 'title', |
| | | children: 'children', |
| | | } |
| | | const detailObjectionStatus = ref('') |
| | |
| | | |
| | | // 获取部门树 |
| | | function getDeptTreeFun() { |
| | | getDeptTree().then(res => { |
| | | getDeptTreeListApi().then(res => { |
| | | deptTree.value = res.data.data |
| | | }) |
| | | } |
| | |
| | | <el-dialog |
| | | class="gd-dialog" |
| | | v-model="visible" |
| | | :title="titleEnum[dialogMode]" |
| | | :title="dialogTitle" |
| | | @closed="visible = false" |
| | | destroy-on-close |
| | | :close-on-click-modal="false" |
| | |
| | | const dialogMode = ref('add') // 弹框模式 |
| | | const submitting = ref(false) // 提交中 |
| | | const dialogReadonly = computed(() => dialogMode.value === 'view') |
| | | const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' }) |
| | | |
| | | const rules = { |
| | | rejectReason: fieldRules(true, 200), |
| | |
| | | } |
| | | |
| | | let formLabelStr = ref('原因') |
| | | let dialogTitle = ref('新增') |
| | | |
| | | |
| | | // 操作类型:1.同意签收、2.拒绝签收 、3.撤回任务 、4.同意审核、 5.拒绝审核、 6.验收通过、 7.验收拒绝 |
| | | async function open({ mode = 'add', row, formLabel, type } = {}) { |
| | | async function open({ mode = 'add', row, formLabel, type,title } = {}) { |
| | | dialogMode.value = mode |
| | | formLabelStr.value = formLabel |
| | | dialogTitle.value = dialogMode.value === 'add' ? title : '查看' |
| | | formData.value = { |
| | | id: row.id, |
| | | auditStatus: type, |
| | |
| | | </template> |
| | | <template v-if="permission.flyOrder_controlReview"> |
| | | <el-button v-if="taskStatus === '3'" @click="addDescription" color="#F2F3F5">驳回</el-button> |
| | | <el-button v-if="taskStatus === '3'" @click="statusChange(4)" color="#4C34FF">同意</el-button> |
| | | <el-button v-if="taskStatus === '3'" @click="statusChange(4)" color="#4C34FF">通过</el-button> |
| | | </template> |
| | | </template> |
| | | <RefuseOrderDialog1 |
| | |
| | | const getAirName = inject('getAirName') |
| | | |
| | | const gdStatusObj = { |
| | | '0': { reason: '拒绝原因', operationType: '2' }, |
| | | '0': { reason: '拒绝原因', operationType: '2',title:'拒绝签收' }, |
| | | '1': { reason: '拒绝原因' }, |
| | | '2': { reason: '' }, |
| | | '3': { reason: '驳回原因', operationType: '5' }, |
| | | '3': { reason: '驳回原因', operationType: '5',title:'驳回' }, |
| | | '4': { reason: '驳回原因' }, |
| | | '5': { reason: '' }, |
| | | '6': { reason: '拒绝原因' }, |
| | |
| | | row: formData.value, |
| | | type: gdStatusObj[taskStatus.value].operationType, |
| | | formLabel: gdStatusObj[taskStatus.value].reason, |
| | | title: gdStatusObj[taskStatus.value]?.title |
| | | }) |
| | | }) |
| | | } |
| | |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="content" style="display: flex"> |
| | | <div class="processBox" v-if="dialogMode !== 'add' && processList.length"> |
| | | <div class="processBox" v-if="dialogMode !== 'add'"> |
| | | <div class="detail-title">工单记录</div> |
| | | <div class="process" > |
| | | <OrderStepBar :processList="processList"/> |
| | | <div class="process"> |
| | | <OrderStepBar :processList="processList" /> |
| | | </div> |
| | | </div> |
| | | <div class="leftBox"> |
| | |
| | | import { gdManageDeviceListApi } from './gdManageDeviceApi' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import * as Cesium from 'cesium' |
| | | import { DrawPolygon } from '@/utils/cesium/DrawPolygon' |
| | | import { DrawPolygon } from '@ztzf/utils' |
| | | import { pxToRem } from '@/utils/rem' |
| | | import dayjs from 'dayjs' |
| | | import RefuseOrderDialog from '@/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue' |
| | |
| | | viewPlane = viewer.entities?.add({ |
| | | customType: 'control_group', |
| | | position: Cesium.Cartesian3.fromDegrees(result[0], result[1]), |
| | | polygon: { |
| | | hierarchy: Cesium.Cartesian3.fromDegreesArray(result), |
| | | material: Cesium.Color.fromBytes(45, 140, 240, 99), |
| | | outline: false, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | }, |
| | | polyline: { |
| | | positions: Cesium.Cartesian3.fromDegreesArray(result), |
| | | clampToGround: true, |
| | | width: 3, |
| | | material: Cesium.Color.RED, |
| | | width: 2, |
| | | material: Cesium.Color.fromBytes(45, 140, 240, 255), |
| | | }, |
| | | }) |
| | | flyVisual({ |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .process{ |
| | | .process { |
| | | height: 100%; |
| | | padding: 12px 16px 24px 16px; |
| | | width: 312px; |
| | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { fieldRules, getDictLabel } from '@ztzf/utils' |
| | | import { fieldRules } from '@ztzf/utils' |
| | | import { gdWorkOrderHandleStatusApi } from '@/views/orderView/orderManage/orderManage/orderManageApi' |
| | | |
| | | // 初始化表单数据 |
| | |
| | | const dialogMode = ref('add') // 弹框模式 |
| | | const submitting = ref(false) // 提交中 |
| | | const dialogReadonly = computed(() => dialogMode.value === 'view') |
| | | const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' }) |
| | | |
| | | const rules = { |
| | | rejectReason: fieldRules(true, 200), |
| File was renamed from applications/task-work-order/src/utils/cesium/DrawPolygon.js |
| | |
| | | import * as Cesium from 'cesium' |
| | | import * as turf from '@turf/turf' |
| | | import { boxTransformScale } from '@/utils/turfFunc' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getPointPositionsHeight } from '@/utils/cesium/mapUtil' |
| | | import { flyVisual } from '@ztzf/utils' |
| | | import { flyVisual, getPointPositionsHeight } from './index' |
| | | import { MapTooltip } from './MapTooltip' |
| | | |
| | | /** |
| | | * 多边形绘制与编辑工具类 |
| | |
| | | this.delPoint = this.delPoint.bind(this) |
| | | // 外部订阅者 |
| | | this.listeners = [] |
| | | // 绘制提示 tooltip |
| | | this.drawingTooltip = null |
| | | } |
| | | |
| | | // 实体命名常量 |
| | |
| | | const cartesian = this.viewer.scene.pickPosition(movement.endPosition) |
| | | if (!cartesian) return |
| | | |
| | | // 更新绘制提示 tooltip 位置 |
| | | if (this.drawingMode && this.drawingTooltip) { |
| | | this.drawingTooltip.move(movement.endPosition) |
| | | } |
| | | |
| | | // 编辑模式下,拖拽点实时更新 |
| | | if (this.editingMode && this.draggedEntity?.customData) { |
| | | this.draggedEntity.position = cartesian |
| | |
| | | finishDrawing() { |
| | | this.curPolygon.positions.pop() |
| | | |
| | | // 隐藏绘制提示 |
| | | if (this.drawingTooltip) { |
| | | this.drawingTooltip.hide() |
| | | } |
| | | |
| | | if (this.curPolygon.positions.length >= 3) { |
| | | this.drawingMode = false |
| | | this.editingMode = true |
| | |
| | | this.removeMenuPopup() |
| | | this.notify('getPolygonPositions', []) |
| | | this.startDrawing() |
| | | |
| | | // 重新显示绘制提示 |
| | | if (this.drawingTooltip) { |
| | | this.drawingTooltip.show('左键点击绘制,双击结束绘制') |
| | | } |
| | | } |
| | | // 删除图斑 |
| | | delSpot() { |
| | |
| | | this.addPosition(item, false) |
| | | }) |
| | | |
| | | const boxTransformScale = (data, multiple = 3) => { |
| | | const line = turf.lineString(data) |
| | | const bbox = turf.bbox(line) |
| | | const bboxPolygon = turf.bboxPolygon(bbox) |
| | | const scaledPolygon = turf.transformScale(bboxPolygon, multiple) |
| | | |
| | | return turf.bbox(scaledPolygon) |
| | | } |
| | | |
| | | // 视角飞入区域 |
| | | const newBox = boxTransformScale( |
| | | positions.map(item => [item.lng, item.lat]), |
| | |
| | | initHandler(viewer) { |
| | | this.viewer = viewer |
| | | this.startDrawing() |
| | | |
| | | // 初始化绘制提示 tooltip |
| | | if (!this.drawingTooltip) { |
| | | this.drawingTooltip = new MapTooltip(viewer) |
| | | this.drawingTooltip.show('左键点击绘制,双击结束绘制') |
| | | } |
| | | |
| | | if (!this.handler) { |
| | | this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) |
| | |
| | | this.removeEntities() |
| | | this.removeHandler() |
| | | this.enableMapControl() |
| | | |
| | | // 销毁绘制提示 tooltip |
| | | if (this.drawingTooltip) { |
| | | this.drawingTooltip.destroy() |
| | | this.drawingTooltip = null |
| | | } |
| | | } |
| | | } |
| | |
| | | import * as Cesium from 'cesium' |
| | | import { MapTooltip } from './MapTooltip' |
| | | import { DrawPolygon } from './DrawPolygon' |
| | | /** |
| | | * 飞到中心点并且所有点在可视范围 |
| | | * @param positionsData 二维数组,每项为 [lon, lat] 或 三维数组 [lon, lat, height] |
| | |
| | | }) |
| | | } |
| | | |
| | | export { MapTooltip, flyVisual } |
| | | // 批量获取点数组经纬度对应高度信息 |
| | | const getPointPositionsHeight = (data, viewer, droneHeight = null) => { |
| | | return new Promise((resolve, reject) => { |
| | | if (!data || !data.length) { |
| | | resolve([]) |
| | | return |
| | | } |
| | | // 假设 viewer 已经初始化并且 terrainProvider 是有效的 |
| | | const terrainProvider = viewer?.terrainProvider |
| | | // 创建 Cartographic 对象 |
| | | const cartographics = data.map(item => { |
| | | const { lng, lat } = item |
| | | return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat)) |
| | | }) |
| | | // 获取地形数据的Promise |
| | | const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics) |
| | | // 使用 Cesium.when 处理 Promise |
| | | promise |
| | | .then(function (updatedPositions) { |
| | | // 是一个数组,包含更新后的 Cartographic 对象 |
| | | const newPosition = updatedPositions.map((item, index) => { |
| | | const longitude = Cesium.Math.toDegrees(item.longitude) |
| | | const latitude = Cesium.Math.toDegrees(item.latitude) |
| | | let pointData = { |
| | | ...data[index], |
| | | longitude, |
| | | latitude, |
| | | ASL: Number(item.height), |
| | | customHeight: Number(item.height), |
| | | } |
| | | if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight) |
| | | return pointData |
| | | }) |
| | | resolve(newPosition) |
| | | }) |
| | | .catch(function (error) { |
| | | console.log('获取高程时发生错误:', error) |
| | | resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat }))) |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | export { MapTooltip, flyVisual, getPointPositionsHeight, DrawPolygon } |
| | |
| | | { |
| | | "name": "@ztzf/utils", |
| | | "version": "1.0.0", |
| | | "private": true, |
| | | "description": "工具类", |
| | | "main": "index.js", |
| | | "license": "ISC", |
| | | "peerDependencies": { |
| | | "cesium": "catalog:", |
| | | "dayjs": "catalog:", |
| | | "decimal.js": "catalog:" |
| | | }, |
| | | "devDependencies": { |
| | | "cesium": "catalog:", |
| | | "dayjs": "catalog:", |
| | | "decimal.js": "catalog:" |
| | | } |
| | | "name": "@ztzf/utils", |
| | | "version": "1.0.0", |
| | | "private": true, |
| | | "description": "工具类", |
| | | "main": "index.js", |
| | | "license": "ISC", |
| | | "peerDependencies": { |
| | | "@turf/turf": "catalog:", |
| | | "cesium": "catalog:", |
| | | "dayjs": "catalog:", |
| | | "decimal.js": "catalog:", |
| | | "element-plus": "catalog:" |
| | | }, |
| | | "devDependencies": { |
| | | "@turf/turf": "catalog:", |
| | | "cesium": "catalog:", |
| | | "dayjs": "catalog:", |
| | | "decimal.js": "catalog:", |
| | | "element-plus": "catalog:" |
| | | } |
| | | } |
| | |
| | | |
| | | packages/utils: |
| | | devDependencies: |
| | | '@turf/turf': |
| | | specifier: 'catalog:' |
| | | version: 6.5.0 |
| | | '@ztzf/utils': |
| | | specifier: workspace:* |
| | | version: 'link:' |
| | | cesium: |
| | | specifier: 'catalog:' |
| | | version: 1.126.0 |
| | |
| | | decimal.js: |
| | | specifier: 'catalog:' |
| | | version: 10.6.0 |
| | | element-plus: |
| | | specifier: 'catalog:' |
| | | version: 2.9.11(vue@3.5.27(typescript@5.9.3)) |
| | | |
| | | uniapps/work-app: |
| | | dependencies: |
| | |
| | | const smallUrl = detail.eventImageUrl |
| | | imageArr.push(smallUrl) |
| | | } |
| | | if (detail.update_photo_url) { |
| | | const smallUrl = detail.update_photo_url |
| | | imageArr.push(smallUrl) |
| | | } |
| | | // if (detail.update_photo_url) { |
| | | // const smallUrl = detail.update_photo_url |
| | | // imageArr.push(smallUrl) |
| | | // } |
| | | return imageArr |
| | | }) |
| | | // 跳转地图 |
| | |
| | | title: workDetailData.value.event_name || '工单详情', |
| | | summary: '查看工单详情', |
| | | href: 'https://example.com/workDetail?eventNum=' + workDetailData.value.event_num, |
| | | imageUrl: workDetailData.value.photo_url, |
| | | imageUrl: workDetailData.value.eventImageUrl, |
| | | success: () => { |
| | | uni.showToast({ title: '分享成功', icon: 'success' }) |
| | | onShareModalClose() |