Merge remote-tracking branch 'origin/master'
18 files modified
2 files added
| | |
| | | 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 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 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() |
| | |
| | | }) |
| | | billboard.id = droneId |
| | | const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration) |
| | | dronePickMap.set(droneId, { |
| | | data: { |
| | | ...item, |
| | | flightHeightM: item.flightHeightM ?? points[0].height, |
| | | flightSpeedMs: item.flightSpeedMs ?? speedMs, |
| | | longitude: item.longitude ?? points[0].longitude, |
| | | latitude: item.latitude ?? points[0].latitude, |
| | | }, |
| | | billboard, |
| | | }) |
| | | dronePickMap.set(droneId, { |
| | | data: { |
| | | ...item, |
| | | flightHeightM: item.flightHeightM ?? points[0].height, |
| | | flightSpeedMs: item.flightSpeedMs ?? speedMs, |
| | | longitude: item.longitude ?? points[0].longitude, |
| | | latitude: item.latitude ?? points[0].latitude, |
| | | }, |
| | | billboard, |
| | | }) |
| | | if (isFavorited(item)) { |
| | | createFavoritePulseEntity(billboard) |
| | | } |
| | | droneTrackRuntime.push({ |
| | | positions, |
| | | billboard, |
| | |
| | | }) |
| | | viewer.clock.multiplier = 1 |
| | | startDroneTrackAnimation() |
| | | startFavoritePulseAnimation() |
| | | reorderCockpitPrimitives() |
| | | } |
| | | |
| | |
| | | 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 |
| | | }) |
| | | } |
| | |
| | | * @Author : yuan |
| | | * @Date : 2026-01-08 09:29:07 |
| | | * @LastEditors : yuan |
| | | * @LastEditTime : 2026-01-27 12:01:37 |
| | | * @LastEditTime : 2026-01-27 14:49:56 |
| | | * @FilePath : \applications\drone-command\src\views\dataCockpit\components\RealWarning.vue |
| | | * @Description : |
| | | * Copyright 2026 OBKoro1, All Rights Reserved. |
| | |
| | | --> |
| | | <template> |
| | | <div class="real-warning" v-loading="props.loading" element-loading-background="rgba(5, 5, 15, 0.6)" |
| | | element-loading-text="???..."> |
| | | element-loading-text="加载中..."> |
| | | <div class="list-content"> |
| | | <EmptyState v-if="!props.data.length && !props.loading" /> |
| | | <RealTemplate v-else v-for="(item, ind) in props.data" :key="ind" :data="item" |
| | |
| | | <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> |
| | |
| | | <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; |
| | | } |
| | | } |
| | | } |
| | |
| | | <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> |
| | |
| | | // 懒加载获取区域节点数据 |
| | | 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 || [] |
| | | |
| | | // 处理返回的节点数据 |
| | |
| | | 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) |
| | | } |
| | | } |
| | | |
| | | 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) |
| | | }) |
| | | } |
| | | // 分享模态框状态 |