Merge remote-tracking branch 'origin/master'
29 files modified
2 files added
| | |
| | | @close="closePopup" /> |
| | | |
| | | <DronePopup :visible="popupVisible && isDronePopup" :position="popupPosition" :drone="selectedDevice" |
| | | :favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="toggleDroneFavorite" |
| | | :favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="handleDroneFavorite" |
| | | @signal="handleDroneSignal" @counter="handleDroneCounter" /> |
| | | </div> |
| | | </template> |
| | |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | alarmDrones: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | leftCollapsed: { |
| | | type: Boolean, |
| | | default: false, |
| | |
| | | default: true, |
| | | }, |
| | | }) |
| | | const emit = defineEmits(['droneSignal', 'droneCounter']) |
| | | const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite']) |
| | | |
| | | const DEFAULT_ZONE_PAGE_SIZE = 999 |
| | | |
| | |
| | | let droneTrackTickHandler = null |
| | | let droneTrackStartTime = null |
| | | let droneTrackRuntime = [] |
| | | 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 detailVisible = ref(true) |
| | | const clusterVisible = ref(false) |
| | | const countyCenterMap = new Map() |
| | |
| | | */ |
| | | |
| | | const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin')) |
| | | |
| | | const isFavorited = item => { |
| | | const value = item?.favorited ?? item?.isFavorite |
| | | return value === 1 || value === '1' || value === true |
| | | } |
| | | |
| | | let pulseCanvas = null |
| | | const getPulseCanvas = () => { |
| | | if (pulseCanvas) return pulseCanvas |
| | | const size = 256 |
| | | const canvas = document.createElement('canvas') |
| | | canvas.width = size |
| | | canvas.height = size |
| | | const ctx = canvas.getContext('2d') |
| | | const center = size / 2 |
| | | const radius = center - 2 |
| | | const gradient = ctx.createRadialGradient(center, center, radius * 0.1, center, center, radius) |
| | | gradient.addColorStop(0, 'rgba(255,59,48,0.55)') |
| | | gradient.addColorStop(0.35, 'rgba(255,59,48,0.35)') |
| | | gradient.addColorStop(0.7, 'rgba(255,59,48,0.15)') |
| | | gradient.addColorStop(1, 'rgba(255,59,48,0)') |
| | | ctx.fillStyle = gradient |
| | | ctx.beginPath() |
| | | ctx.arc(center, center, radius, 0, Math.PI * 2) |
| | | ctx.fill() |
| | | pulseCanvas = canvas |
| | | return canvas |
| | | } |
| | | |
| | | const createFavoritePulseEntity = (billboard) => { |
| | | if (!viewer || !billboard) return |
| | | const material = new Cesium.ImageMaterialProperty({ |
| | | image: getPulseCanvas(), |
| | | transparent: true, |
| | | }) |
| | | const pulse = { |
| | | material, |
| | | offset: Math.random() * PULSE_DURATION_S, |
| | | entity: null, |
| | | } |
| | | 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), |
| | | material, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | }, |
| | | }) |
| | | favoritePulseEntities.push(pulse) |
| | | } |
| | | |
| | | const getDevicePosition = item => { |
| | | const longitudeRaw = item.longitude ?? item.lng ?? item.lon |
| | |
| | | } |
| | | } |
| | | |
| | | const getPulseRadius = pulse => { |
| | | const phase = ((favoritePulseElapsed + pulse.offset) % PULSE_DURATION_S) / PULSE_DURATION_S |
| | | const wave = phase < 0.5 ? phase * 2 : (1 - phase) * 2 |
| | | return PULSE_MIN_RADIUS_M + (PULSE_MAX_RADIUS_M - PULSE_MIN_RADIUS_M) * wave |
| | | } |
| | | |
| | | const setDetailVisibility = visible => { |
| | | detailVisible.value = visible |
| | | if (defenseZonePrimitive) defenseZonePrimitive.show = visible |
| | |
| | | const setDroneVisibility = visible => { |
| | | if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible |
| | | if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible |
| | | if (favoritePulseEntities.length) { |
| | | favoritePulseEntities.forEach(pulse => { |
| | | if (pulse?.entity) pulse.entity.show = visible |
| | | }) |
| | | } |
| | | if (!visible && selectedTargetType.value === 'drone') { |
| | | closePopup() |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | const buildSimulatedTrackPoints = () => { |
| | | return [ |
| | | { |
| | | longitude: 114.963191, |
| | | latitude: 27.136716, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.957308, |
| | | latitude: 27.138452, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.952, |
| | | latitude: 27.136317, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.949293, |
| | | latitude: 27.133864, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.944666, |
| | | latitude: 27.130526, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.945909, |
| | | latitude: 27.127845, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.962974, |
| | | latitude: 27.136242, |
| | | height: 120 |
| | | }, |
| | | const buildSimulatedTrackPoints = center => { |
| | | const basePoints = [ |
| | | [ |
| | | { |
| | | longitude: 114.963191, |
| | | latitude: 27.136716, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.957308, |
| | | latitude: 27.138452, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.952, |
| | | latitude: 27.136317, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.949293, |
| | | latitude: 27.133864, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.944666, |
| | | latitude: 27.130526, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.945909, |
| | | latitude: 27.127845, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.962974, |
| | | latitude: 27.136242, |
| | | height: 120 |
| | | }, |
| | | ], |
| | | |
| | | [ |
| | | { |
| | | longitude: 114.931063, |
| | | latitude: 27.095052, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.928653, |
| | | latitude: 27.096307, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.925958, |
| | | latitude: 27.096963, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.925836, |
| | | latitude: 27.096102, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.928384, |
| | | latitude: 27.095866, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.929874, |
| | | latitude: 27.096095, |
| | | height: 120 |
| | | }, |
| | | { |
| | | longitude: 114.931048, |
| | | latitude: 27.097666, |
| | | height: 120 |
| | | }, |
| | | ] |
| | | ] |
| | | |
| | | return basePoints[center.trackIndex] |
| | | } |
| | | |
| | | |
| | | const clearDroneTrackEntities = () => { |
| | | if (!viewer) return |
| | | stopDroneTrackAnimation() |
| | | stopFavoritePulseAnimation() |
| | | if (droneTrackBillboardCollection) { |
| | | if (!droneTrackBillboardCollection.isDestroyed?.()) { |
| | | removeCockpitPrimitive(droneTrackBillboardCollection) |
| | |
| | | droneTrackPolylineCollection = null |
| | | } |
| | | droneTrackRuntime = [] |
| | | if (favoritePulseEntities.length) { |
| | | favoritePulseEntities.forEach(pulse => { |
| | | if (pulse?.entity) viewer?.entities?.remove(pulse.entity) |
| | | }) |
| | | favoritePulseEntities.length = 0 |
| | | } |
| | | dronePickMap.clear() |
| | | if (selectedTargetType.value === 'drone') { |
| | | closePopup() |
| | |
| | | droneTrackTickHandler = null |
| | | } |
| | | |
| | | const renderSimulatedDroneTrack = () => { |
| | | const startFavoritePulseAnimation = () => { |
| | | if (!viewer || favoritePulseRafId) return |
| | | favoritePulseStartAt = performance.now() |
| | | const tick = now => { |
| | | if (!viewer || viewer.isDestroyed?.() || !favoritePulseEntities.length) { |
| | | favoritePulseRafId = null |
| | | return |
| | | } |
| | | favoritePulseElapsed = (now - favoritePulseStartAt) / 1000 |
| | | viewer.scene.requestRender() |
| | | favoritePulseRafId = requestAnimationFrame(tick) |
| | | } |
| | | favoritePulseRafId = requestAnimationFrame(tick) |
| | | } |
| | | |
| | | const stopFavoritePulseAnimation = () => { |
| | | if (favoritePulseRafId) { |
| | | cancelAnimationFrame(favoritePulseRafId) |
| | | favoritePulseRafId = null |
| | | } |
| | | } |
| | | |
| | | const renderSimulatedDroneTrack = (list) => { |
| | | if (!viewer) return |
| | | clearDroneTrackEntities() |
| | | if (!list?.length) return |
| | | ensureDroneTrackCollections() |
| | | droneTrackBillboardCollection.show = detailVisible.value |
| | | droneTrackPolylineCollection.show = detailVisible.value |
| | | droneTrackRuntime = [] |
| | | const baseCenters = [['默认区域', { longitude: 114.958541, latitude: 27.121917 }]] |
| | | const segmentDuration = 6 |
| | | const baseTrackColor = Cesium.Color.fromCssColorString('red') |
| | | baseCenters.forEach(([centerName, center], trackIndex) => { |
| | | const points = buildSimulatedTrackPoints(center) |
| | | if (points.length < 2) return |
| | | const positions = points.map(point => |
| | | Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height) |
| | | ) |
| | | let trackMaterial = createDroneTrackMaterial({ |
| | | color: baseTrackColor, |
| | | speed: 4.5, |
| | | headWidth: 0.2, |
| | | glowPower: 1.8, |
| | | backgroundAlpha: 0.34, |
| | | }) |
| | | if (!trackMaterial) { |
| | | trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor }) |
| | | } |
| | | droneTrackPolylineCollection.add({ |
| | | positions, |
| | | width: 3, |
| | | material: trackMaterial, |
| | | }) |
| | | const droneId = `drone-track-${trackIndex}` |
| | | const billboard = droneTrackBillboardCollection.add({ |
| | | position: positions[positions.length - 1], |
| | | image: droneIcon, |
| | | width: 36, |
| | | height: 36, |
| | | verticalOrigin: Cesium.VerticalOrigin.CENTER, |
| | | disableDepthTestDistance: Number.POSITIVE_INFINITY, |
| | | }) |
| | | billboard.id = droneId |
| | | const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration) |
| | | ; (list || []).forEach((item, trackIndex) => { |
| | | const position = getDevicePosition(item) |
| | | if (!position) return |
| | | const points = buildSimulatedTrackPoints({ ...position, height: item.flightHeightM, trackIndex }) |
| | | if (points.length < 2) return |
| | | const positions = points.map(point => |
| | | Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height) |
| | | ) |
| | | let trackMaterial = createDroneTrackMaterial({ |
| | | color: baseTrackColor, |
| | | speed: 4.5, |
| | | headWidth: 0.2, |
| | | glowPower: 1.8, |
| | | backgroundAlpha: 0.34, |
| | | }) |
| | | if (!trackMaterial) { |
| | | trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor }) |
| | | } |
| | | droneTrackPolylineCollection.add({ |
| | | positions, |
| | | 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, |
| | | }) |
| | | billboard.id = droneId |
| | | const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration) |
| | | dronePickMap.set(droneId, { |
| | | data: { |
| | | droneName: `无人机-${trackIndex + 1}`, |
| | | droneSerialNo: `UAV-${trackIndex + 1}`, |
| | | flightHeightM: points[0].height, |
| | | flightSpeedMs: speedMs, |
| | | longitude: points[0].longitude, |
| | | latitude: points[0].latitude, |
| | | flightStatus: 1, |
| | | deviceName: centerName, |
| | | dataSource: centerName, |
| | | counterDeviceName: centerName, |
| | | ...item, |
| | | flightHeightM: item.flightHeightM ?? points[0].height, |
| | | flightSpeedMs: item.flightSpeedMs ?? speedMs, |
| | | longitude: item.longitude ?? points[0].longitude, |
| | | latitude: item.latitude ?? points[0].latitude, |
| | | }, |
| | | billboard, |
| | | }) |
| | | droneTrackRuntime.push({ |
| | | positions, |
| | | billboard, |
| | | segmentDuration, |
| | | duration: (points.length - 1) * segmentDuration, |
| | | if (isFavorited(item)) { |
| | | createFavoritePulseEntity(billboard) |
| | | } |
| | | droneTrackRuntime.push({ |
| | | positions, |
| | | billboard, |
| | | segmentDuration, |
| | | duration: (points.length - 1) * segmentDuration, |
| | | }) |
| | | }) |
| | | }) |
| | | viewer.clock.multiplier = 1 |
| | | startDroneTrackAnimation() |
| | | startFavoritePulseAnimation() |
| | | reorderCockpitPrimitives() |
| | | } |
| | | |
| | |
| | | const popupPosition = ref({ x: 0, y: 0 }) |
| | | const popupVisible = computed(() => Boolean(selectedDevice.value)) |
| | | const isDronePopup = computed(() => selectedTargetType.value === 'drone') |
| | | const toggleDroneFavorite = () => { |
| | | const handleDroneFavorite = () => { |
| | | if (!selectedDevice.value) return |
| | | selectedDevice.value.isFavorite = !selectedDevice.value.isFavorite |
| | | emit('droneFavorite', selectedDevice.value) |
| | | } |
| | | const handleDroneSignal = () => emit('droneSignal', selectedDevice.value) |
| | | const handleDroneCounter = () => emit('droneCounter', selectedDevice.value) |
| | |
| | | () => props.onlineDevices, |
| | | devices => { |
| | | renderDeviceEntities(devices || []) |
| | | }, |
| | | { deep: true } |
| | | ) |
| | | |
| | | watch( |
| | | () => props.alarmDrones, |
| | | list => { |
| | | renderSimulatedDroneTrack(list || []) |
| | | if (selectedTargetType.value === 'drone' && selectedDevice.value) { |
| | | const selectedId = selectedDevice.value.alarmRecordId ?? selectedDevice.value.id |
| | | const match = (list || []).find(item => (item?.alarmRecordId ?? item?.id) === selectedId) |
| | | if (match) selectedDevice.value = { ...selectedDevice.value, ...match } |
| | | } |
| | | }, |
| | | { deep: true } |
| | | ) |
| | |
| | | loadPartitions() |
| | | loadAggregation() |
| | | loadCommandPosts() |
| | | renderSimulatedDroneTrack() |
| | | renderSimulatedDroneTrack(props.alarmDrones) |
| | | initDeviceClickHandler() |
| | | } |
| | | |
| | |
| | | this.initHandler(viewer) |
| | | this.isPureSpotPreview = isPurePreview |
| | | this.startDrawing() |
| | | let newPosition = positions.map(item => { |
| | | const normalizedPositions = this.normalizePolygonPositions(positions) |
| | | let newPosition = normalizedPositions.map(item => { |
| | | return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height || 0)) |
| | | }) |
| | | // 预览航线的时候调用 |
| | |
| | | }) |
| | | |
| | | // 视角飞入区域 |
| | | const newBox = boxTransformScale( |
| | | positions.map(item => [item.lng, item.lat]), |
| | | 5 |
| | | ) |
| | | const newBox = boxTransformScale(normalizedPositions.map(item => [item.lng, item.lat]), 5) |
| | | viewer.camera.flyTo({ |
| | | destination: Cesium.Rectangle.fromDegrees(...newBox), |
| | | offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0), |
| | | duration: 0.5, |
| | | }) |
| | | |
| | | let pointList = await getPointPositionsHeight(positions, viewer) |
| | | let pointList = await getPointPositionsHeight(normalizedPositions, viewer) |
| | | flyVisual({ positionsData: pointList.map(item => [item.lng, item.lat, item.ASL]), viewer }) |
| | | |
| | | this.drawingMode = false |
| | |
| | | this.rebuildMidPoints() |
| | | } |
| | | |
| | | // 规范化多边形点位:如果首尾重复,去掉末尾闭合点 |
| | | normalizePolygonPositions(positions) { |
| | | if (!Array.isArray(positions) || positions.length < 2) return positions || [] |
| | | const first = positions[0] |
| | | const last = positions[positions.length - 1] |
| | | const sameLng = Number(first?.lng) === Number(last?.lng) |
| | | const sameLat = Number(first?.lat) === Number(last?.lat) |
| | | const sameHeight = Number(first?.height || 0) === Number(last?.height || 0) |
| | | if (sameLng && sameLat && sameHeight) { |
| | | return positions.slice(0, -1) |
| | | } |
| | | return positions |
| | | } |
| | | |
| | | // 初始化事件处理器 |
| | | initHandler(viewer) { |
| | | this.viewer = viewer |
| | |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <div class="label">触发条件</div> |
| | | <div class="val">{{ formData.triggerCondition }}</div> |
| | | <div class="val"> |
| | | {{ getDictLabel(formData.triggerCondition, dictObj.triggeringCondition) }} |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <div class="label">响应机制</div> |
| | |
| | | maxlength="200" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="触发条件" prop="triggerCondition"> |
| | | <el-input class="command-input" v-model="formData.triggerCondition" |
| | | maxlength="200" placeholder="请输入" clearable /> |
| | | <el-select class="command-select" popper-class="command-select-popper" |
| | | v-model="formData.triggerCondition" placeholder="请选择" clearable> |
| | | <el-option v-for="item in dictObj.triggeringCondition" :key="item.dictKey" |
| | | :label="item.dictValue" :value="item.dictKey" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="管控级别" prop="controlLevel"> |
| | | <el-select class="command-select" popper-class="command-select-popper" |
| | |
| | | // 获取设备列表 |
| | | async function getDeviceList () { |
| | | if (deviceOptions.value.length) return |
| | | const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id }) |
| | | const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 }) |
| | | deviceOptions.value = res?.data?.data ?? [] |
| | | if (dialogMode.value === 'view') { |
| | | deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id)) |
| | |
| | | {{ getDictLabel(row.areaType, dictObj.areaType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="triggerCondition" show-overflow-tooltip width="130" label="触发条件" /> |
| | | <el-table-column prop="triggerCondition" show-overflow-tooltip width="130" label="触发条件"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.triggerCondition, dictObj.triggeringCondition) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="responseMechanism" show-overflow-tooltip width="130" label="响应机制" /> |
| | | <el-table-column prop="controlLevel" show-overflow-tooltip width="120" label="管控级别"> |
| | | <template v-slot="{ row }"> |
| | |
| | | const dictObj = ref({ |
| | | areaType: [], //区域类型 |
| | | controlLevel: [], //管控级别 |
| | | triggeringCondition: [], //触发条件 |
| | | deviceType: [], |
| | | }) |
| | | provide('dictObj', dictObj) |
| | | |
| | | // 获取字典 |
| | | function getDictList() { |
| | | getDictionaryByCode('areaType,controlLevel,deviceType').then(res => { |
| | | getDictionaryByCode('areaType,controlLevel,deviceType,triggeringCondition').then(res => { |
| | | dictObj.value = res.data.data |
| | | }) |
| | | } |
| | |
| | | <el-table-column prop="charger" label="负责人" /> |
| | | <el-table-column prop="contactPhone" label="联系电话" /> |
| | | <el-table-column prop="outTarget" label="出库去向" /> |
| | | <el-table-column prop="outTime" label="出库时间" /> |
| | | |
| | | <el-table-column prop="outTime" show-overflow-tooltip label="出库时间"> |
| | | <template v-slot="{ row }"> |
| | | {{ dayjs(row.outTime).format('YYYY-MM-DD') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="purpose" label="用途" /> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="command-table-pagination"> |
| | | <el-pagination popper-class="command-select-popper" |
| | | v-model:current-page="searchParams.current" v-model:page-size="searchParams.size" |
| | | layout="total, prev, pager, next, sizes" :total="total" @change="getList" /> |
| | | <el-pagination popper-class="command-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> |
| | | </div> |
| | |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设备名称" prop="deviceName"> |
| | | <el-input class="command-input" v-model="formData.deviceName" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.deviceName" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设备编码" prop="deviceSn"> |
| | | <el-input class="command-input" v-model="formData.deviceSn" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.deviceSn" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="型号" prop="deviceModel"> |
| | | <el-input class="command-input" v-model="formData.deviceModel" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.deviceModel" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="规格" prop="deviceSpecification"> |
| | | <el-input class="command-input" v-model="formData.deviceSpecification" |
| | | maxlength="50" placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.deviceSpecification" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="生产厂商" prop="manufacturer"> |
| | | <el-input class="command-input" v-model="formData.manufacturer" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.manufacturer" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="来源" prop="source"> |
| | | <el-input class="command-input" v-model="formData.source" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.source" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="用途" prop="purpose"> |
| | | <el-input class="command-input" v-model="formData.purpose" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.purpose" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="所属部门" prop="belongDept"> |
| | | <el-tree-select class="command-select" |
| | | popper-class="command-tree-select-popper" v-model="formData.belongDept" |
| | | node-key="id" :data="deptTree" :props="treeProps" check-strictly @change="belongDeptChange" |
| | | clearable /> |
| | | <el-tree-select class="command-select" popper-class="command-tree-select-popper" |
| | | v-model="formData.belongDept" node-key="id" :data="deptTree" :props="treeProps" |
| | | check-strictly @change="belongDeptChange" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="联系电话" prop="contactPhone"> |
| | | <el-input class="command-input" v-model="formData.contactPhone" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.contactPhone" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button> |
| | | <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' |
| | | }}</el-button> |
| | | <el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting" |
| | | :disabled="submitting" @click="handleSubmit"> |
| | | 保存 |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import dayjs from 'dayjs' |
| | | |
| | | import { computed, ref } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { fwDeviceDetailApi, fwDeviceSubmitApi } from '@/views/basicManage/deviceStock/fwDevice' |
| | |
| | | </div> |
| | | |
| | | <div class="content"> |
| | | <component :is="currentComponent" /> |
| | | <RealWarning |
| | | v-if="activeIdModel === 1" |
| | | :data="realWarningData" |
| | | :loading="realWarningLoading" |
| | | @signal="emit('real-warning-signal', $event)" |
| | | @counter="emit('real-warning-counter', $event)" |
| | | @favorite="emit('real-warning-favorite', $event)" |
| | | /> |
| | | <EquipmentWarning v-else-if="activeIdModel === 2" /> |
| | | <HistoryWarning v-else /> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | |
| | | <script setup> |
| | | import TitleTemplate from './templateComponents/TitleTemplate.vue' |
| | | import RealWarning from './RealWarning.vue' |
| | | import EquipmentWarning from './EquipmentWarning.vue' |
| | | import HistoryWarning from './HistoryWarning.vue' |
| | | import { alarmStatisticsApi } from '@/api/dataCockpit' |
| | | import { onMounted } from 'vue' |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import RealWarning from './RealWarning.vue' |
| | | |
| | | const props = defineProps({ |
| | | activeId: { |
| | |
| | | collapsed: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | realWarningData: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | realWarningLoading: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:activeId', 'update:collapsed']) |
| | | const emit = defineEmits([ |
| | | 'update:activeId', |
| | | 'update:collapsed', |
| | | 'real-warning-signal', |
| | | 'real-warning-counter', |
| | | 'real-warning-favorite' |
| | | ]) |
| | | |
| | | const activeIdModel = computed({ |
| | | get: () => props.activeId, |
| | |
| | | component: HistoryWarning |
| | | } |
| | | ]) |
| | | |
| | | const currentComponent = computed(() => { |
| | | return categoryList.value.find(i => i.id === activeIdModel.value)?.component |
| | | }) |
| | | |
| | | const toggleCollapse = () => { |
| | | collapsedModel.value = !collapsedModel.value |
| | |
| | | * @Author : yuan |
| | | * @Date : 2026-01-08 09:29:07 |
| | | * @LastEditors : yuan |
| | | * @LastEditTime : 2026-01-26 09:59:52 |
| | | * @LastEditTime : 2026-01-27 14:49:56 |
| | | * @FilePath : \applications\drone-command\src\views\dataCockpit\components\RealWarning.vue |
| | | * @Description : |
| | | * Copyright 2026 OBKoro1, All Rights Reserved. |
| | | * 2026-01-08 09:29:07 |
| | | --> |
| | | <template> |
| | | <div |
| | | class="real-warning" |
| | | v-loading="loading" |
| | | element-loading-background="rgba(5, 5, 15, 0.6)" |
| | | element-loading-text="加载中..." |
| | | > |
| | | <div class="real-warning" v-loading="props.loading" element-loading-background="rgba(5, 5, 15, 0.6)" |
| | | element-loading-text="加载中..."> |
| | | <div class="list-content"> |
| | | <EmptyState v-if="!realWarningData.length" /> |
| | | <RealTemplate |
| | | v-else |
| | | v-for="(item, ind) in realWarningData" |
| | | :key="ind" |
| | | :data="item" |
| | | @signal="() => handleAction('signal', item)" |
| | | @counter="() => handleAction('counter', item)" |
| | | @favorite="() => handleFavorite(item)" |
| | | /> |
| | | <EmptyState v-if="!props.data.length && !props.loading" /> |
| | | <RealTemplate v-else v-for="(item, ind) in props.data" :key="ind" :data="item" |
| | | @signal="emit('signal', item)" @counter="emit('counter', item)" @favorite="emit('favorite', item)" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import RealTemplate from './templateComponents/RealTemplate.vue' |
| | | import { |
| | | alarmLogApi, |
| | | alarmFavoriteRemoveApi, |
| | | alarmFavoriteSaveApi, |
| | | interferenceAndExpulsionApi |
| | | } from '@/api/dataCockpit' |
| | | import { onMounted, ref } from 'vue' |
| | | import EmptyState from './EmptyState.vue' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | const realWarningData = ref([]) |
| | | const loading = ref(true) |
| | | const minLoadingMs = 400 |
| | | |
| | | const actionTextMap = { |
| | | signal: '信号干扰', |
| | | counter: '诱导驱离' |
| | | } |
| | | |
| | | |
| | | const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id |
| | | const getFavoriteId = (item) => |
| | | item?.favoriteId ?? |
| | | item?.alarmFavoriteId ?? |
| | | item?.fwAlarmFavoriteId ?? |
| | | item?.favoriteRecordId ?? |
| | | getAlarmRecordId(item) |
| | | |
| | | const isFavorited = (item) => { |
| | | const value = item?.favorited ?? item?.isFavorite |
| | | return value === 1 || value === '1' || value === true |
| | | } |
| | | |
| | | const buildPayload = (type, item) => ({ |
| | | counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '', |
| | | alarmRecordId: getAlarmRecordId(item) |
| | | const props = defineProps({ |
| | | data: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | loading: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }) |
| | | |
| | | const handleAction = async (type, item) => { |
| | | const actionText = actionTextMap[type] || '操作' |
| | | try { |
| | | const res = await interferenceAndExpulsionApi(buildPayload(type, item)) |
| | | if (res?.data?.success) { |
| | | ElMessage({ type: 'success', message: actionText + '成功' }) |
| | | await fetchRealWarning() |
| | | } else { |
| | | ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' }) |
| | | } |
| | | } catch (error) { |
| | | ElMessage({ type: 'error', message: actionText + '失败' }) |
| | | } |
| | | } |
| | | |
| | | const handleFavorite = async (item) => { |
| | | const alarmRecordId = getAlarmRecordId(item) |
| | | if (!alarmRecordId) { |
| | | ElMessage({ type: 'warning', message: '缺少告警记录ID' }) |
| | | return |
| | | } |
| | | const isFavorite = isFavorited(item) |
| | | const actionText = isFavorite ? '取消关注' : '关注' |
| | | try { |
| | | const res = isFavorite |
| | | ? await alarmFavoriteRemoveApi(getFavoriteId(item)) |
| | | : await alarmFavoriteSaveApi({ alarmRecordId }) |
| | | if (res?.data?.success) { |
| | | ElMessage({ type: 'success', message: actionText + '成功' }) |
| | | await fetchRealWarning() |
| | | } else { |
| | | ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' }) |
| | | } |
| | | } catch (error) { |
| | | ElMessage({ type: 'error', message: actionText + '失败' }) |
| | | } |
| | | } |
| | | |
| | | const fetchRealWarning = async () => { |
| | | const startAt = Date.now() |
| | | loading.value = true |
| | | try { |
| | | const params = { current: 1, size: 20, alarmType: '1' } |
| | | const res = await alarmLogApi(params) |
| | | const records = res?.data?.data?.records ?? [] |
| | | realWarningData.value = records.map((record) => ({ |
| | | ...record, |
| | | isFavorite: |
| | | record?.isFavorite ?? |
| | | (record?.favorited === 1 || record?.favorited === '1') |
| | | })) |
| | | } finally { |
| | | const elapsed = Date.now() - startAt |
| | | if (elapsed < minLoadingMs) { |
| | | await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed)) |
| | | } |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchRealWarning() |
| | | }) |
| | | const emit = defineEmits(['signal', 'counter', 'favorite']) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | <div class="row"> |
| | | <div class="col"> |
| | | <span class="label">有效范围</span> |
| | | {{ data.range }} |
| | | {{ Number(data.range).toFixed(0) }}m |
| | | </div> |
| | | <div class="col"> |
| | | <span class="label">设备型号</span> |
| | |
| | | <div class="page-container"> |
| | | <DeviceMapContainer |
| | | :online-devices="onlineDevices" |
| | | :alarm-drones="realWarningData" |
| | | :left-collapsed="leftCollapsed" |
| | | container-id="data-cockpit-map" |
| | | @drone-signal="handleAlarmAction('signal', $event)" |
| | | @drone-counter="handleAlarmAction('counter', $event)" |
| | | @drone-favorite="handleAlarmFavorite" |
| | | /> |
| | | <LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed" /> |
| | | <LeftContainer |
| | | class="left-container" |
| | | v-model:activeId="leftActiveId" |
| | | v-model:collapsed="leftCollapsed" |
| | | :real-warning-data="realWarningData" |
| | | :real-warning-loading="realWarningLoading" |
| | | @real-warning-signal="handleAlarmAction('signal', $event)" |
| | | @real-warning-counter="handleAlarmAction('counter', $event)" |
| | | @real-warning-favorite="handleAlarmFavorite" |
| | | /> |
| | | <RightContainer class="right-container" v-model:collapsed="rightCollapsed" v-model:onlineDevices="onlineDevices" /> |
| | | <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" /> |
| | | <LegendBar class="legend-container" /> |
| | |
| | | import RightContainer from './components/RightContainer.vue'; |
| | | import CenterContainer from './components/CenterContainer.vue'; |
| | | import LegendBar from './components/LegendBar.vue'; |
| | | import { |
| | | alarmLogApi, |
| | | alarmFavoriteRemoveApi, |
| | | alarmFavoriteSaveApi, |
| | | interferenceAndExpulsionApi |
| | | } from '@/api/dataCockpit'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import { onMounted, ref } from 'vue'; |
| | | |
| | | const leftActiveId = ref(1); |
| | | const leftCollapsed = ref(false); |
| | | |
| | | const rightCollapsed = ref(false); |
| | | const onlineDevices = ref([]); |
| | | const realWarningData = ref([]); |
| | | const realWarningLoading = ref(true); |
| | | const minLoadingMs = 400; |
| | | |
| | | const onSelectWarning = (type) => { |
| | | leftActiveId.value = type; |
| | |
| | | const onSelectEquipment = () => { |
| | | rightCollapsed.value = false; |
| | | }; |
| | | |
| | | const actionTextMap = { |
| | | signal: '信号干扰', |
| | | counter: '诱导驱离' |
| | | }; |
| | | |
| | | const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id; |
| | | const getFavoriteId = (item) => |
| | | item?.favoriteId ?? |
| | | item?.alarmFavoriteId ?? |
| | | item?.fwAlarmFavoriteId ?? |
| | | item?.favoriteRecordId ?? |
| | | getAlarmRecordId(item); |
| | | |
| | | const isFavorited = (item) => { |
| | | const value = item?.favorited ?? item?.isFavorite; |
| | | return value === 1 || value === '1' || value === true; |
| | | }; |
| | | |
| | | const buildPayload = (type, item) => ({ |
| | | counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '', |
| | | alarmRecordId: getAlarmRecordId(item) |
| | | }); |
| | | |
| | | const handleAlarmAction = async (type, item) => { |
| | | const actionText = actionTextMap[type] || '操作'; |
| | | try { |
| | | const res = await interferenceAndExpulsionApi(buildPayload(type, item)); |
| | | if (res?.data?.success) { |
| | | ElMessage({ type: 'success', message: actionText + '成功' }); |
| | | await fetchRealWarning(); |
| | | } else { |
| | | ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' }); |
| | | } |
| | | } catch (error) { |
| | | ElMessage({ type: 'error', message: actionText + '失败' }); |
| | | } |
| | | }; |
| | | |
| | | const handleAlarmFavorite = async (item) => { |
| | | const alarmRecordId = getAlarmRecordId(item); |
| | | if (!alarmRecordId) { |
| | | ElMessage({ type: 'warning', message: '缺少告警记录ID' }); |
| | | return; |
| | | } |
| | | const favorite = isFavorited(item); |
| | | const actionText = favorite ? '取消关注' : '关注'; |
| | | try { |
| | | const res = favorite |
| | | ? await alarmFavoriteRemoveApi(getFavoriteId(item)) |
| | | : await alarmFavoriteSaveApi({ alarmRecordId }); |
| | | if (res?.data?.success) { |
| | | ElMessage({ type: 'success', message: actionText + '成功' }); |
| | | await fetchRealWarning(); |
| | | } else { |
| | | ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' }); |
| | | } |
| | | } catch (error) { |
| | | ElMessage({ type: 'error', message: actionText + '失败' }); |
| | | } |
| | | }; |
| | | |
| | | const fetchRealWarning = async () => { |
| | | const startAt = Date.now(); |
| | | realWarningLoading.value = true; |
| | | try { |
| | | const params = { current: 1, size: 20, alarmType: '1' }; |
| | | const res = await alarmLogApi(params); |
| | | const records = res?.data?.data?.records ?? []; |
| | | realWarningData.value = records.map((record) => ({ |
| | | ...record, |
| | | isFavorite: |
| | | record?.isFavorite ?? |
| | | (record?.favorited === 1 || record?.favorited === '1') |
| | | })); |
| | | } finally { |
| | | const elapsed = Date.now() - startAt; |
| | | if (elapsed < minLoadingMs) { |
| | | await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed)); |
| | | } |
| | | realWarningLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | fetchRealWarning(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="部门类型" prop="deptCategory"> |
| | | <el-select class="command-select" popper-class="command-select-popper" v-model="formData.deptCategory" placeholder="请选择" clearable> |
| | | <el-option v-for="item in dictObj.org_category" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> |
| | | <el-option v-for="item in dictObj.org_category" :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="deptNature"> |
| | | <el-select class="command-select" popper-class="command-select-popper" v-model="formData.deptNature" placeholder="请选择" clearable> |
| | | <el-option v-for="item in dictObj.org_nature" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> |
| | | <el-option v-for="item in dictObj.org_nature" :key="item.dictKey" :label="item.dictValue" :value="Number(item.dictKey)" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | fullName: '', |
| | | parentId: '', |
| | | parentName: '', |
| | | deptCategory: '', |
| | | deptNature: '', |
| | | areaCode: '', |
| | | deptCategory: null, |
| | | deptNature: null, |
| | | areaCode: [], |
| | | areaName: '', |
| | | deploymentMode: 0, |
| | | sort: 0, |
| | |
| | | const areaProps = { |
| | | label: 'title', |
| | | value: 'value', |
| | | emitPath: false, |
| | | emitPath: true, |
| | | checkStrictly: true, |
| | | lazy: true, |
| | | lazyLoad(node, resolve) { |
| | |
| | | } |
| | | |
| | | function getFullAreaCode(areaCode) { |
| | | if (!areaCode) return '' |
| | | if (!areaCode) return [] |
| | | if (Array.isArray(areaCode)) return areaCode |
| | | const code = areaCode.toString() |
| | | if (code.includes(',')) return code |
| | | if (code.includes(',')) return code.split(',') |
| | | if (code.endsWith('0000000000')) { |
| | | return code |
| | | return [code] |
| | | } else if (code.endsWith('00000000') && !code.endsWith('0000000000')) { |
| | | const provinceCode = code.substring(0, 2) + '0000000000' |
| | | return `${provinceCode},${code}` |
| | | return [provinceCode, code] |
| | | } else { |
| | | const provinceCode = code.substring(0, 2) + '0000000000' |
| | | const cityCode = code.substring(0, 4) + '00000000' |
| | | return `${provinceCode},${cityCode},${code}` |
| | | return [provinceCode, cityCode, code] |
| | | } |
| | | } |
| | | |
| | | function normalizeNumber(value) { |
| | | if (value === '' || value === null || value === undefined) return null |
| | | const num = Number(value) |
| | | return Number.isNaN(num) ? null : num |
| | | } |
| | | |
| | | function handleCancel() { |
| | |
| | | const data = res?.data?.data ?? {} |
| | | formData.value = { |
| | | ...data, |
| | | deptCategory: normalizeNumber(data.deptCategory), |
| | | deptNature: normalizeNumber(data.deptNature), |
| | | areaCode: getFullAreaCode(data.areaCode), |
| | | } |
| | | } |
| | |
| | | color="#1A2652" |
| | | type="primary" |
| | | :disabled="selectedIds.length !== 1" |
| | | v-if="userInfo.role_name?.includes('admin')" |
| | | v-if="permission.permissionRole_set" |
| | | @click="handleRole" |
| | | > |
| | | 权限设置 |
| | |
| | | const dialogRef = ref(null) |
| | | const roleDialogRef = ref(null) |
| | | const store = useStore() |
| | | const userInfo = computed(() => store.getters.userInfo || {}) |
| | | const permission = computed(() => store.getters.permission || {}) |
| | | const route = useRoute() |
| | | |
| | | async function loadList() { |
| | |
| | | .detail-container { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | height: 680px; |
| | | |
| | | .detail-left { |
| | | margin-right: 20px; |
| | |
| | | } |
| | | |
| | | .detail-right { |
| | | &>div { |
| | | .detail-title { |
| | | margin: 10px 0; |
| | | } |
| | | width: 920px; |
| | | flex: 1; |
| | | overflow: auto; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | &:first-child { |
| | | .detail-title { |
| | | margin-top: 0; |
| | | } |
| | | } |
| | | .view-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .gd-cesium { |
| | | width: 100%; |
| | | min-height: 380px; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | |
| | | }) |
| | | |
| | | const formRef = ref(null) |
| | | const formData = reactive({ |
| | | |
| | | const initForm = () => ({ |
| | | flyerName: '', |
| | | flyerId: '', |
| | | flyerPhone: '', |
| | |
| | | technicalStrength: '', |
| | | skilledUavType: '', |
| | | certification: [], // 证书 |
| | | regionCode: '', // 区域编码 |
| | | }) |
| | | |
| | | const formData = ref(initForm()) // 表单数据 |
| | | |
| | | |
| | | // const rules = { |
| | | // flyerId: fieldRules(true), |
| | | // flyerPhone: fieldRules(true, 50), |
| | | // flightHours: fieldRules(true, 50), |
| | | // technicalSkills: fieldRules(true, 50), |
| | | // skilledTaskType: fieldRules(true), |
| | | // projectExperience: fieldRules(true), |
| | | // technicalStrength: fieldRules(true), |
| | | // skilledUavType: fieldRules(true, 50), |
| | | // } |
| | | |
| | | const rules = { |
| | | flyerId: [ |
| | |
| | | watch( |
| | | () => props.flyingHandData, |
| | | (newData) => { |
| | | formData.value = initForm() |
| | | if (newData) { |
| | | Object.assign(formData, newData) |
| | | formData.flightHours = Number(newData.flightHours) |
| | | } |
| | | Object.assign(formData.value, newData) |
| | | formData.value.flightHours = Number(newData.flightHours) |
| | | } |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ) |
| | |
| | | // 处理选择飞手姓名 |
| | | function handleChange(val) { |
| | | const result = flyingHandList.value.find(item => item.id === val) || [] |
| | | formData.flyerName = result.name |
| | | formData.certification = formData.certification.concat(result.licenseFrontFileUrl || [], result.licenseBackFileUrl || []) |
| | | formData.value.flyerName = result.name |
| | | formData.value.regionCode = result.regionCode |
| | | formData.value.certification = formData.value.certification.concat(result.licenseFrontFileUrl || [], result.licenseBackFileUrl || []) |
| | | } |
| | | |
| | | |
| | |
| | | function resetForm() { |
| | | formRef.value?.resetFields() |
| | | // 重置为默认值 |
| | | Object.assign(formData, { |
| | | Object.assign(formData.value, { |
| | | flyerName: '', |
| | | flyerPhone: '', |
| | | flightHours: 0, |
| | |
| | | |
| | | try { |
| | | if (props.dialogType === 'add') { |
| | | await submitFlyingHand(formData) |
| | | await submitFlyingHand(formData.value) |
| | | ElMessage.success('添加成功') |
| | | } else if (props.dialogType === 'edit') { |
| | | await submitFlyingHand(formData) |
| | | await submitFlyingHand(formData.value) |
| | | ElMessage.success('编辑成功') |
| | | } |
| | | emit('confirm') |
| | |
| | | <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange"> |
| | | <el-table-column label="序号" width="70" align="center"> |
| | | <template #default="{ $index }"> |
| | | {{ formatNumber((searchParams.current - 1) * searchParams.size + $index + 1) }} |
| | | {{ $index + 1 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="caseName" show-overflow-tooltip label="案例名称" /> |
| | |
| | | <el-table class="gd-table" :data="list"> |
| | | <el-table-column label="序号" width="70"> |
| | | <template v-slot="{ $index }"> |
| | | {{ String($index + 1).padStart(2, '0') }} |
| | | {{ $index + 1 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="title" show-overflow-tooltip label="标题" /> |
| | |
| | | <el-table-column prop="otherObjectionDetail" show-overflow-tooltip label="其他异议详情" /> |
| | | <el-table-column prop="submitter" show-overflow-tooltip label="问题提交人" /> |
| | | <el-table-column prop="submitterContact" show-overflow-tooltip width="120" label="提交人联系方式" /> |
| | | <el-table-column prop="submitUnitName" show-overflow-tooltip label="提交单位" /> |
| | | <el-table-column prop="createDeptName" show-overflow-tooltip label="提交单位" /> |
| | | <el-table-column prop="handleUnit" show-overflow-tooltip label="处理单位" /> |
| | | <el-table-column prop="objectionDesc" show-overflow-tooltip label="异议描述" /> |
| | | <el-table-column prop="objectionBasis" show-overflow-tooltip label="异议依据" /> |
| | |
| | | <el-table class="gd-table" :data="list"> |
| | | <el-table-column label="序号" width="80"> |
| | | <template v-slot="{ $index }"> |
| | | {{ ((searchParams.current - 1) * searchParams.size + $index + 1).toString().padStart(2, '0') }} |
| | | {{ $index + 1 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="title" show-overflow-tooltip label="标题" /> |
| | |
| | | <el-table class="gd-table" :data="list"> |
| | | <el-table-column label="序号" width="70" align="center"> |
| | | <template #default="scope"> |
| | | {{ ((searchParams.current - 1) * searchParams.size + scope.$index + 1).toString().padStart(2, '0') }} |
| | | {{ scope.$index + 1 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="demandName" show-overflow-tooltip label="需求名称" /> |
| | |
| | | :title="titleEnum[dialogMode]" |
| | | @closed="visible = false" |
| | | destroy-on-close |
| | | width="auto" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="content" style="display: flex"> |
| | | <div class="processBox" v-if="dialogMode !== 'add' && processList.length"> |
| | | <div class="detail-container" style="display: flex"> |
| | | <div class="detail-left" v-if="dialogMode !== 'add' && processList.length"> |
| | | <el-timeline class="gd-timeline"> |
| | | <el-timeline-item |
| | | v-for="(activity, index) in processList" |
| | |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </div> |
| | | <div class="leftBox"> |
| | | |
| | | <div class="detail-right"> |
| | | <!-- 查看模式 --> |
| | | <div class="detail-container" v-if="dialogReadonly"> |
| | | <div class="view-container" v-if="dialogReadonly"> |
| | | <div class="detail-title">工单详情</div> |
| | | <el-row class="detail-row-view"> |
| | | <el-col :span="12"> |
| | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .content { |
| | | display: flex; |
| | | gap: 0 20px; |
| | | height: 680px; |
| | | |
| | | .leftBox { |
| | | width: 920px; |
| | | flex: 1; |
| | | overflow: auto; |
| | | display: flex; |
| | | flex-direction: column; |
| | | .gd-cesium { |
| | | width: 100%; |
| | | min-height: 380px; |
| | | flex: 1; |
| | | } |
| | | } |
| | | .processBox { |
| | | width: 312px; |
| | | padding: 24px 0; |
| | | border: 1px solid #e4e4e4; |
| | | overflow: auto; |
| | | |
| | | .item-content { |
| | | position: relative; |
| | | .flowName { |
| | | width: 80px; |
| | | position: absolute; |
| | | left: -120px; |
| | | top: 0px; |
| | | } |
| | | } |
| | | } |
| | | .imgBox { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | > div { |
| | | width: calc((100% - 40px) / 5); /* 5列布局,减去4个10px间隙 */ |
| | | height: 110px; |
| | | flex-shrink: 0; |
| | | } |
| | | .el-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | popper-class="gd-tree-select-popper" |
| | | v-model="formData.areaCode" |
| | | node-key="id" |
| | | :default-expanded-keys="expandedKeys" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'name', |
| | |
| | | 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 rules = { |
| | | deptName: fieldRules(true), |
| | |
| | | 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 loadRegionNode(node, resolve) { |
| | | try { |
| | | const parentCode = node.data?.id || '360800000000' |
| | | const res = await regionLazyTreeApi({ parentCode }) |
| | | // 初始化加载时传递 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: item.id || item.value, |
| | | id: nodeId, |
| | | name: item.name || item.title || item.label, |
| | | hasChildren: item.hasChildren || false |
| | | } |
| | |
| | | resolve([]) |
| | | } |
| | | } |
| | | // 获取初始区域数据(根节点) |
| | | async function getRegionList() { |
| | | try { |
| | | // 懒加载模式下,只需要初始化根节点 |
| | | const res = await regionLazyTreeApi({ parentCode: '360800000000' }) |
| | | const nodes = res?.data?.data || [] |
| | | |
| | | // 处理根节点数据 |
| | | regionOptions.value = nodes.map(item => { |
| | | return { |
| | | id: item.id || item.value, |
| | | name: item.name || item.title || item.label, |
| | | hasChildren: item.hasChildren || false |
| | | } |
| | | }) |
| | | } catch (error) { |
| | | console.error('获取区域数据失败:', error) |
| | | ElMessage.error('获取区域数据失败') |
| | | } |
| | | } |
| | | |
| | | defineExpose({ open }) |
| | | |
| | | onMounted(() => { |
| | | getRegionList() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | <el-table class="gd-table" :data="list"> |
| | | <el-table-column label="序号" width="80"> |
| | | <template v-slot="{ $index }"> |
| | | {{ ((searchParams.current - 1) * searchParams.size + $index + 1).toString().padStart(2, '0') }} |
| | | {{ $index + 1 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="deptCode" show-overflow-tooltip label="机构编码" /> |
| | |
| | | {{ getDictLabel(row.status, dictObj.institutionStatus) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" class-name="operation-btns" width="310"> |
| | | <el-table-column label="操作" class-name="operation-btns" width="400"> |
| | | <template v-slot="{ row }"> |
| | | <el-link type="primary" @click="openForm('view', row)">查看</el-link> |
| | | <el-link type="primary" @click="openForm('edit', row)">编辑</el-link> |
| | |
| | | async function loadRegionNode(node, resolve) { |
| | | |
| | | try { |
| | | const parentCode = node.data?.id || '360800000000' |
| | | const res = await regionLazyTreeApi({ parentCode }) |
| | | // 初始化加载时传递 code 参数,加载子节点时传递 parentCode 参数 |
| | | const params = node.data?.id ? { parentCode: node.data.id } : { code: '360800000000' } |
| | | const res = await regionLazyTreeApi(params) |
| | | const nodes = res?.data?.data || [] |
| | | |
| | | // 处理返回的节点数据 |
| | |
| | | } |
| | | } |
| | | |
| | | // 获取初始区域数据(根节点) |
| | | async function getRegionList() { |
| | | try { |
| | | // 懒加载模式下,只需要初始化根节点 |
| | | const res = await regionLazyTreeApi({ parentCode: '360800000000' }) |
| | | const nodes = res?.data?.data || [] |
| | | |
| | | // 处理根节点数据 |
| | | regionOptions.value = nodes.map(item => { |
| | | return { |
| | | id: item.id || item.value, |
| | | name: item.name || item.title || item.label, |
| | | hasChildren: item.hasChildren || false |
| | | } |
| | | }) |
| | | } catch (error) { |
| | | console.error('获取区域数据失败:', error) |
| | | } |
| | | } |
| | | |
| | | |
| | | // 获取列表 |
| | | async function getList() { |
| | |
| | | |
| | | onMounted(() => { |
| | | getDictList() |
| | | getRegionList() |
| | | getList() |
| | | }) |
| | | </script> |
| 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.code }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">区划名称</div> |
| | | <div class="val">{{ formData.name }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">上级区划</div> |
| | | <div class="val">{{ formData.parentName }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">区划级别</div> |
| | | <div class="val"> {{ getDictLabel(formData.regionLevel, dictObj.divisionLevel) }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">区划排序</div> |
| | | <div class="val">{{ formData.sort }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">区划状态</div> |
| | | <div class="val"> {{ getDictLabel(formData.status, dictObj.zoningStatus) }}</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="code"> |
| | | <el-input class="gd-input" v-model="formData.code" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="区划名称" prop="name"> |
| | | <el-input class="gd-input" v-model="formData.name" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="上级区划" prop="parentCode"> |
| | | <el-tree-select |
| | | class="gd-select" |
| | | popper-class="gd-tree-select-popper" |
| | | v-model="formData.parentCode" |
| | | node-key="id" |
| | | :default-expanded-keys="expandedKeys" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'name', |
| | | children: 'children' |
| | | }" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | :check-strictly="true" |
| | | lazy |
| | | :load="loadRegionNode" |
| | | :render-after-expand="false" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="区划级别" prop="regionLevel"> |
| | | <el-select |
| | | class="gd-select" |
| | | popper-class="gd-select-popper" |
| | | v-model="formData.regionLevel" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option v-for="item in dictObj.divisionLevel" :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="sort"> |
| | | <el-input class="gd-input" v-model="formData.sort" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="区划状态" prop="status"> |
| | | <el-select |
| | | class="gd-select" |
| | | popper-class="gd-select-popper" |
| | | v-model="formData.status" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option v-for="item in dictObj.zoningStatus" :key="item.dictKey" |
| | | :label="item.dictValue" |
| | | :value="item.dictKey" /> |
| | | </el-select> |
| | | </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, nextTick } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { fieldRules } from '@ztzf/utils' |
| | | import { |
| | | zoningSubmitApi, |
| | | zoningDetailApi, |
| | | regionLazyTreeApi |
| | | } from './zoningApi' |
| | | |
| | | // 初始化表单数据 |
| | | const initForm = () => ({ |
| | | code: '', |
| | | name: '', |
| | | parentCode: '', |
| | | regionLevel: '', |
| | | sort: '', |
| | | status: '', |
| | | id: null, |
| | | parentName: '' |
| | | }) |
| | | |
| | | const dictObj = inject('dictObj') |
| | | const regionOptions = inject('regionOptions') |
| | | |
| | | 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 rules = { |
| | | code: fieldRules(true), |
| | | name: fieldRules(true), |
| | | parentCode: fieldRules(true), |
| | | regionLevel: fieldRules(true), |
| | | sort: fieldRules(true), |
| | | status: fieldRules(true), |
| | | } |
| | | |
| | | // 提交新增/编辑 |
| | | async function handleSubmit() { |
| | | const isValid = await formRef.value?.validate().catch(() => false) |
| | | if (!isValid) return |
| | | submitting.value = true |
| | | try { |
| | | await zoningSubmitApi(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 zoningDetailApi({ id: formData.value.id }) |
| | | const data = res?.data?.data ?? {} |
| | | // 将 status 和 regionLevel 转换为字符串类型,与字典中的 dictKey 类型保持一致 |
| | | if (data.status !== undefined) { |
| | | data.status = String(data.status) |
| | | } |
| | | if (data.regionLevel !== undefined) { |
| | | data.regionLevel = String(data.regionLevel) |
| | | } |
| | | // 将 parentCode 转换为字符串类型,与 regionOptions 中的 value 类型保持一致 |
| | | if (data.parentCode !== undefined) { |
| | | data.parentCode = String(data.parentCode) |
| | | } |
| | | formData.value = data |
| | | |
| | | // 如果是编辑模式,获取区域节点的路径以展开树形选择器 |
| | | if (dialogMode.value === 'edit' && data.parentCode) { |
| | | await loadRegionPath(data.parentCode) |
| | | } |
| | | } |
| | | |
| | | // 加载区域节点路径 |
| | | async function loadRegionPath(parentCode) { |
| | | 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 === parentCode) { |
| | | 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 { |
| | | // 初始化加载时传递 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 }) |
| | | |
| | | onMounted(() => { |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | 区划管理 |
| | | </div> |
| | | <basic-container> |
| | | <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form"> |
| | | <el-form-item label="上级区划名称" prop="parentName"> |
| | | <el-input |
| | | class="gd-input gray" |
| | | v-model="searchParams.parentName" |
| | | placeholder="请输入" |
| | | clearable |
| | | @clear="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="区划名称" prop="districtName"> |
| | | <el-input |
| | | class="gd-input gray" |
| | | v-model="searchParams.districtName" |
| | | placeholder="请输入" |
| | | clearable |
| | | @clear="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="区划代码" prop="cityCode"> |
| | | <el-input |
| | | class="gd-input gray" |
| | | v-model="searchParams.cityCode" |
| | | placeholder="请输入" |
| | | clearable |
| | | @clear="handleSearch" |
| | | /> |
| | | </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> |
| | | <el-button :icon="Upload" @click="handleImport">导入区划</el-button> |
| | | <el-button :icon="Download" @click="handleExport">导出区划</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="code" show-overflow-tooltip label="区划代码" /> |
| | | <el-table-column prop="name" show-overflow-tooltip label="区划名称" /> |
| | | <el-table-column prop="parentName" show-overflow-tooltip label="上级区划名称" /> |
| | | <el-table-column prop="regionLevel" show-overflow-tooltip label="区划级别"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.regionLevel, dictObj.divisionLevel) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="sort" show-overflow-tooltip label="排序" /> |
| | | <el-table-column prop="status" show-overflow-tooltip label="区划状态"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.status, dictObj.zoningStatus) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" class-name="operation-btns" width="400"> |
| | | <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> |
| | | <el-link type="primary" @click="toggleStatus(row)">{{row.status === 1 ? '禁用' : '启用'}}</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" /> |
| | | |
| | | <!-- 导入区划弹框 --> |
| | | <el-dialog |
| | | class="gd-dialog" |
| | | append-to-body |
| | | v-model="isShowImportView" |
| | | title="导入区划" |
| | | :width="pxToRem(600)" |
| | | :close-on-click-modal="false" |
| | | :destroy-on-close="true" |
| | | @close="handleImportClose" |
| | | > |
| | | <el-form class="gd-dialog-form" ref="importFormRef" :model="importParams" :rules="importRules" label-width="140px"> |
| | | <el-form-item label="上传文件" prop="file"> |
| | | {{ importFileName }} |
| | | <el-upload class="avatar-uploader" action="" :show-file-list="false" :before-upload="onImportFileBefore"> |
| | | <el-button size="small" type="primary">点击上传</el-button> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button color="#F2F3F5" @click="isShowImportView = false">取消</el-button> |
| | | <el-button |
| | | class="" |
| | | color="#4C34FF" |
| | | :loading="importSubmitting" |
| | | :disabled="importSubmitting" |
| | | @click="submitImport(importFormRef)" |
| | | > |
| | | 导入 |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </basic-container> |
| | | </template> |
| | | |
| | | <script setup > |
| | | /** |
| | | * @description index |
| | | * @date 2026-01-26 (周一) 10:50:43 |
| | | */ |
| | | defineOptions({ |
| | | name: 'index' |
| | | <script setup> |
| | | import { Search, RefreshRight, Plus, Upload, Download } from '@element-plus/icons-vue' |
| | | import { onMounted, ref, provide, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox, ElUpload } from 'element-plus' |
| | | import FormDiaLog from './FormDiaLog.vue' |
| | | import { |
| | | zoningPageApi, |
| | | zoningRemoveApi, |
| | | zoningImportApi, |
| | | zoningExportApi, |
| | | regionLazyTreeApi, |
| | | zoningSubmitApi |
| | | } from './zoningApi' |
| | | import { getDictionaryByCode } from '@/api/system/dictbiz' |
| | | import { getDictLabel } from '@ztzf/utils' |
| | | import { pxToRem } from '@/utils/rem' |
| | | // 初始化查询参数 |
| | | const initSearchParams = () => ({ |
| | | parentName: '', // 上级区划名称 |
| | | districtName: '', // 区划名称 |
| | | cityCode: '', // 区划代码 |
| | | 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 regionOptions = ref([]) // 区域选项 |
| | | const dictObj = ref({}) // 字典对象 |
| | | |
| | | // 导入区划相关 |
| | | const isShowImportView = ref(false) |
| | | const importFileName = ref('') |
| | | const importParams = ref({ |
| | | isCovered: '0', |
| | | file: '', |
| | | }) |
| | | const importFormRef = ref() |
| | | const importSubmitting = ref(false) |
| | | const importRules = ref({ |
| | | file: [{ required: true, message: '请上传文件', trigger: ['change'] }], |
| | | }) |
| | | |
| | | provide('regionOptions', regionOptions) |
| | | provide('dictObj', dictObj) |
| | | |
| | | // 获取字典列表 |
| | | function getDictList() { |
| | | return getDictionaryByCode('zoningStatus,divisionLevel').then(res => { |
| | | dictObj.value = res.data.data |
| | | }) |
| | | } |
| | | |
| | | // 获取列表 |
| | | async function getList() { |
| | | loading.value = true |
| | | try { |
| | | const res = await zoningPageApi(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 zoningRemoveApi({ id: row.id }) |
| | | ElMessage.success('删除成功') |
| | | getList() |
| | | } |
| | | |
| | | // 切换区划状态 |
| | | async function toggleStatus(row) { |
| | | // 计算目标状态:当前为1则切换为0,当前为0则切换为1 |
| | | const targetStatus = row.status === 1 ? 0 : 1 |
| | | const actionText = targetStatus === 0 ? '禁用' : '启用' |
| | | |
| | | await ElMessageBox.confirm(`确认${actionText}该区划吗?`, '提示', { |
| | | type: 'warning', |
| | | customClass: 'gd-confirm-custom', |
| | | confirmButtonClass: 'gd-confirm-button', |
| | | cancelButtonClass: 'gd-confirm-cancel-button', |
| | | }) |
| | | |
| | | try { |
| | | await zoningSubmitApi({ ...row, status: targetStatus }) |
| | | ElMessage.success(`${actionText}成功`) |
| | | getList() |
| | | } catch (error) { |
| | | ElMessage.error(`${actionText}失败`) |
| | | } |
| | | } |
| | | |
| | | // 导入区划 |
| | | function handleImport() { |
| | | isShowImportView.value = true |
| | | } |
| | | |
| | | // 导入区划弹框关闭 |
| | | function handleImportClose() { |
| | | importFileName.value = '' |
| | | importParams.value = { |
| | | isCovered: '0', |
| | | file: '', |
| | | } |
| | | } |
| | | |
| | | // 上传文件前处理 |
| | | function onImportFileBefore(file) { |
| | | // 检查文件类型 |
| | | const allowedTypes = ['.xlsx', '.xls'] |
| | | const fileExtension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase() |
| | | if (!allowedTypes.includes(fileExtension)) { |
| | | ElMessage.error('请上传Excel文件(.xlsx 或 .xls)') |
| | | return false |
| | | } |
| | | let data = new FormData() |
| | | data.append('file', file) |
| | | |
| | | zoningImportApi(data, { isCovered: importParams.value.isCovered }).then(res => { |
| | | if (res.data.code === 200) { |
| | | ElMessage.success('导入成功') |
| | | // 保存文件名 |
| | | importFileName.value = file.name |
| | | importParams.value.file = file |
| | | isShowImportView.value = false |
| | | getList() |
| | | } else { |
| | | ElMessage.error(res.msg || '导入失败') |
| | | } |
| | | }) |
| | | |
| | | return false // 阻止组件的默认上传行为 |
| | | } |
| | | |
| | | // 提交导入 |
| | | async function submitImport(formValidate) { |
| | | if (!formValidate) return |
| | | await formValidate.validate(async (valid, fields) => { |
| | | if (valid) { |
| | | importSubmitting.value = true |
| | | try { |
| | | if (!importParams.value.file) { |
| | | ElMessage.error('请先上传文件') |
| | | return |
| | | } |
| | | // 触发上传操作 |
| | | await onImportFileBefore(importParams.value.file) |
| | | } finally { |
| | | importSubmitting.value = false |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 导出区划 |
| | | function handleExport() { |
| | | zoningExportApi(searchParams.value).then(res => { |
| | | const blob = new Blob([res.data]) |
| | | const url = URL.createObjectURL(blob) |
| | | const a = document.createElement('a') |
| | | a.href = url |
| | | a.download = `区划列表${new Date().getTime()}.xlsx` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | URL.revokeObjectURL(url) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDictList() |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| New file |
| | |
| | | import request from '@/axios' |
| | | |
| | | // 列表 |
| | | export const zoningPageApi = params => { |
| | | return request({ |
| | | url: `/blade-system/region/page`, |
| | | method: 'get', |
| | | params: { ...params }, |
| | | }) |
| | | } |
| | | |
| | | // 新增或编辑 |
| | | export const zoningSubmitApi = data => { |
| | | return request({ |
| | | url: `/blade-system/region/submit`, |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 删除 |
| | | export const zoningRemoveApi = params => { |
| | | return request({ |
| | | url: `/blade-system/region/remove`, |
| | | method: 'post', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // 详情 |
| | | export const zoningDetailApi = params => { |
| | | return request({ |
| | | url: `/blade-system/region/detail`, |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // 导入区划 |
| | | export const zoningImportApi = (data,params) => { |
| | | return request({ |
| | | url: `/blade-system/region/import-region`, |
| | | method: 'post', |
| | | data, |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // 导出区划 |
| | | export const zoningExportApi = params => { |
| | | return request({ |
| | | url: `/blade-system/region/export-region`, |
| | | method: 'get', |
| | | params, |
| | | responseType: 'blob', |
| | | }) |
| | | } |
| | | |
| | | // 懒加载列表 |
| | | export const regionLazyTreeApi = params => { |
| | | return request({ |
| | | url: `/blade-system/region/lazy-tree`, |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | |
| | | uni.showToast({ |
| | | title: '退回成功', |
| | | icon: 'success', |
| | | duration: 1500 |
| | | }) |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }, 1500) |
| | | }) |
| | | |
| | | } |
| | |
| | | uni.showToast({ |
| | | title: '确认成功', |
| | | icon: 'success', |
| | | duration: 1500 |
| | | }) |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }, 1500) |
| | | }) |
| | | } |
| | | // 分享模态框状态 |
| | |
| | | } |
| | | .card { |
| | | display: flex; |
| | | padding: 44rpx; |
| | | padding: 40rpx 0; |
| | | border-bottom: 1px solid #EBEEF3; |
| | | .left { |
| | | width: 70px; |
| | |
| | | color: #86909C; |
| | | margin-bottom: 14rpx; |
| | | } |
| | | // 最后一个txt 不添加margin-bottom |
| | | .txt:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | // 第一个card 不添加下边框 |
| | |
| | | <template> |
| | | <view class="legalPolicy"> |
| | | <!-- <u-navbar title="法律政策" @rightClick="rightClick" :autoBack="true"></u-navbar> --> |
| | | <u-search :bgColor="'white'" v-model="noticeTitle" :show-action="true" actionText="搜索" :animation="true" @custom="handleSearch"></u-search> |
| | | <u-search :bgColor="'white'" v-model="formParams.noticeTitle" :show-action="true" actionText="搜索" :animation="true" @custom="handleSearch"></u-search> |
| | | <view class="tabs-container"> |
| | | <u-tabs :list="tabs" lineColor="#265CFD" @click="tabClick"></u-tabs> |
| | | </view> |
| | |
| | | import docSvg from '@/static/images/doc.png' |
| | | import { ref, onMounted } from 'vue' |
| | | |
| | | const noticeTitle = ref('') |
| | | |
| | | const tabs = ref([ |
| | | { |
| | | name: '法律法规', |
| | |
| | | current: 1, |
| | | size: 10, |
| | | noticeType: 2, |
| | | noticeTitle: noticeTitle.value, |
| | | noticeTitle: '', |
| | | isAsc: true |
| | | }) |
| | | |
| | |
| | | const handleSearch = () => { |
| | | // 搜索时重置为第一页 |
| | | formParams.value.current = 1 |
| | | formParams.value.noticeTitle = formParams.value.noticeTitle.trim() |
| | | getSysNoticeList() |
| | | } |
| | | // 触底加载 |
| | |
| | | |
| | | :deep(.u-button--primary) { |
| | | background-color: #265CFD; |
| | | border-color: #265CFD; |
| | | } |
| | | |
| | | :deep(.u-form) { |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.name" border="none"></u-input> |
| | | <u-input v-model="formParams.name" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="SN(出厂序列号)" |
| | |
| | | :borderBottom="true" |
| | | ref="item2" |
| | | > |
| | | <u-input v-model="formParams.sn" border="none"></u-input> |
| | | <u-input v-model="formParams.sn" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="飞行器厂商" |
| | |
| | | ref="item2" |
| | | > |
| | | <u-input |
| | | v-model="formParams.manufacturer" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | v-model="formParams.manufacturerText" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.model" border="none"></u-input> |
| | | <u-input v-model="formParams.model" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="是否改装" |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.isModified" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.type" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.holder" |
| | | v-model="formParams.holder" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.purchaseDate" border="none" placeholder="请选择日期" suffixIcon="calendar"></u-input> |
| | | <u-input v-model="formParams.purchaseDate" readonly border="none" placeholder="请选择日期" suffixIcon="calendar"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="有无保险" |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.hasInsurance" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.region" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.caacRegistrationCode" |
| | | v-model="formParams.caacRegistrationCode" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.deviceImage" |
| | | v-model="formParams.deviceImage" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.remarks" |
| | | v-model="formParams.remarks" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | current: 1, |
| | | size: 1000, |
| | | }).then(res => { |
| | | manufacturerList.value = res.data.data.records.map(item => ({ |
| | | ...item, |
| | | label: item.unitName, |
| | | value: item.id, |
| | | })) |
| | | // manufacturerList.value = res.data.data.records.map(item => ({ |
| | | // ...item, |
| | | // label: item.unitName, |
| | | // value: item.id, |
| | | // })) |
| | | console.log(formParams.value.manufacturer, res.data.data.records.find(item => item.id === formParams.value.manufacturer)) |
| | | formParams.value.manufacturerText = res.data.data.records.find(item => item.id === formParams.value.manufacturer)?.unitName || '' |
| | | }) |
| | | } |
| | | onLoad((options) => { |
| | |
| | | formParams.value = res.data.data |
| | | formParams.value.isModified = res.data.data.isModified ? '是' : '否' |
| | | formParams.value.hasInsurance = res.data.data.hasInsurance === '1' ? '有' : '无' |
| | | formParams.value.manufacturer = manufacturerList.value.find(item => item.value === res.data.data.manufacturer)?.label || '' |
| | | // formParams.value.manufacturer = manufacturerList.value.find(item => item.value === res.data.data.manufacturer)?.label || '' |
| | | formParams.value.type = typeList.value.find(item => item.value === res.data.data.type)?.label || '' |
| | | formParams.value.region = res.data.data.region |
| | | getManufacturerInfoApi() |
| | | }) |
| | | |
| | | |
| | | }) |
| | | onMounted(async () => { |
| | | // getManufacturerInfoApi() |
| | | getProTypeApi() |
| | | getManufacturerInfoApi() |
| | | // 动态导入xzqhData,确保在使用前已初始化 |
| | | const { xzqhData } = await import('@/static/xzqh/index') |
| | | areaData.value = xzqhData |
| | |
| | | @confirm="onPickerPlanType" |
| | | @cancel="isShowPlanType = false" |
| | | /> |
| | | <up-datetime-picker |
| | | <u-datetime-picker |
| | | :show="isShowStartTime" |
| | | v-model="startTime" |
| | | mode="datetime" |
| | | :min-date="minDate" |
| | | @confirm="onConfirmStartTime" |
| | | @cancel="isShowStartTime = false" |
| | | ></up-datetime-picker> |
| | | <up-datetime-picker |
| | | ></u-datetime-picker> |
| | | <u-datetime-picker |
| | | :show="isShowEndTime" |
| | | v-model="endTime" |
| | | mode="datetime" |
| | | :min-date="minDate" |
| | | @confirm="onConfirmEndTime" |
| | | @cancel="isShowEndTime = false" |
| | | ></up-datetime-picker> |
| | | ></u-datetime-picker> |
| | | <!-- <u-calendar :show="isShowTimeRange" mode="date" @confirm="onConfirmTimeRange" @close="isShowTimeRange = false" :close-on-click-overlay="true"></u-calendar> --> |
| | | <!-- <u-calendar :show="isShowTimeRange" mode="range" @confirm="onConfirmTimeRange" @close="isShowTimeRange = false" :close-on-click-overlay="true" :allow-same-day="true" :show-time="true" time-format="HH:mm:ss"></u-calendar> --> |
| | | <!-- 飞行器 --> |
| | |
| | | const isShowStartTime = ref(false) |
| | | // 是否显示结束时间选择器 |
| | | const isShowEndTime = ref(false) |
| | | const minDate = ref(Date.now()); // 设置最小日期为当前时间戳 |
| | | const startTime = ref(Date.now()); // 设置默认值为当前时间戳 |
| | | const endTime = ref(Date.now()); // 设置默认值为当前时间戳 |
| | | |
| | |
| | | duration: 2000 |
| | | }) |
| | | } |
| | | }).finally(() => { |
| | | isSubmitDisabled.value = false |
| | | }) |
| | | } |
| | | onMounted(() => { |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.flightPlanName" border="none"></u-input> |
| | | <u-input v-model="formParams.flightPlanName" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="任务类型" |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.flightTaskType" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.flightRule" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.flightMode" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.flightStartTime" border="none" suffixIcon="calendar"></u-input> |
| | | <u-input v-model="formParams.flightStartTime" border="none" readonly suffixIcon="calendar"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="飞行活动结束日期" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.flightEndTime" border="none" suffixIcon="calendar"></u-input> |
| | | <u-input v-model="formParams.flightEndTime" readonly border="none" suffixIcon="calendar"></u-input> |
| | | </u-form-item> |
| | | |
| | | <u-form-item |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.planType" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.contactPerson" border="none"></u-input> |
| | | <u-input v-model="formParams.contactPerson" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="联系电话" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.contactPhone" border="none"></u-input> |
| | | <u-input v-model="formParams.contactPhone" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="承办人" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.undertaker" border="none"></u-input> |
| | | <u-input v-model="formParams.undertaker" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="承办人联系电话" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.undertakerPhone" border="none"></u-input> |
| | | <u-input v-model="formParams.undertakerPhone" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="临时空域批件" |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.weatherCondition" |
| | | v-model="formParams.weatherCondition" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.aircraftId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.pilotId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.airspaceId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.routeId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | flightPlanDetailsApi(options.id).then(res => { |
| | | // return |
| | | const result = res.data.data |
| | | console.log(result, '7777') |
| | | formParams.value = result |
| | | formParams.value.routeId = result.routeInfo.name |
| | | formParams.value.airspaceId = result.airspaceInfo.taskName || '无' |
| | | formParams.value.aircraftId = result.airspaceInfo.aircraftInfoList.map(item => item.name).join(',') || '无' |
| | | formParams.value.pilotId = result.operatorInfoList.map(item => item.name).join(',') || '无' |
| | | // 字典 |
| | | formParams.value.flightRule = actionsFlightRule.value.find(item => item.dictValue === result.flightRule)?.dictLabel || '无' |
| | | formParams.value.flightMode = actionsFlightMode.value.find(item => item.dictValue === result.flightMode)?.dictLabel || '无' |
| | | formParams.value.planType = actionsPlanType.value.find(item => item.dictValue === result.planType)?.dictLabel || '无' |
| | | formParams.value.flightTaskType = actionsFlightTaskType.value.find(item => item.dictValue === result.flightTaskType)?.dictLabel || '无' |
| | | // 数据 |
| | | formParams.value.routeId = result?.routeInfo?.name || '无' |
| | | formParams.value.airspaceId = result?.airspaceInfo?.taskName || '无' |
| | | formParams.value.aircraftId = result?.airspaceInfo?.aircraftInfoList?.map(item => item.name).join(',') || '无' |
| | | formParams.value.pilotId = result?.operatorInfoList?.map(item => item.name).join(',') || '无' |
| | | |
| | | }) |
| | | }) |
| | | onMounted(() => { |
| | |
| | | flex: 1; |
| | | max-width: 350rpx; |
| | | height: 166rpx; |
| | | background: rgba(251,253,255,0.57); |
| | | background: rgba(251,253,255,0.8); |
| | | box-shadow: inset 0px 0px 7px 0px rgba(64,156,230,0.14), 0px 1px 3px 0px rgba(35,49,96,0.11); |
| | | border-radius: 8px 8px 8px 8px; |
| | | border: 1px solid #fff; |