diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserMenuConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserMenuConfigController.java new file mode 100644 index 0000000..eec291e --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserMenuConfigController.java @@ -0,0 +1,52 @@ +package com.ruoyi.web.controller.system; + +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.system.domain.SysUserMenuConfig; +import com.ruoyi.system.service.ISysUserMenuConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 用户左侧菜单配置(当前登录用户自己的配置) + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user/menuConfig") +public class SysUserMenuConfigController extends BaseController { + + @Autowired + private ISysUserMenuConfigService menuConfigService; + + /** + * 获取当前用户的左侧菜单配置 + */ + @GetMapping + public AjaxResult getMyConfig() { + Long userId = getUserId(); + SysUserMenuConfig config = menuConfigService.selectByUserId(userId); + if (config == null) { + return success(null); + } + return success(config); + } + + /** + * 保存当前用户的左侧菜单配置 + * 请求体: { "menuItems": "[{...}]", "position": "left" } + */ + @PutMapping + public AjaxResult saveMyConfig(@RequestBody Map body) { + Long userId = getUserId(); + String menuItems = body != null && body.get("menuItems") != null ? body.get("menuItems").toString() : null; + String position = body != null && body.get("position") != null ? body.get("position").toString() : "left"; + if (menuItems == null) { + return error("菜单项不能为空"); + } + int rows = menuConfigService.saveConfig(userId, menuItems, position, getUsername()); + return toAjax(rows); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserMenuConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserMenuConfig.java new file mode 100644 index 0000000..6b08c56 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserMenuConfig.java @@ -0,0 +1,63 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.Size; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 用户左侧菜单配置对象 sys_user_menu_config + * + * @author ruoyi + */ +public class SysUserMenuConfig extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** 配置主键 */ + private Long configId; + + /** 用户ID */ + @Excel(name = "用户ID") + private Long userId; + + /** 菜单项JSON数组 */ + @Excel(name = "菜单项") + private String menuItems; + + /** 菜单位置 left/top/bottom */ + @Excel(name = "菜单位置") + @Size(max = 20) + private String position; + + public Long getConfigId() { + return configId; + } + + public void setConfigId(Long configId) { + this.configId = configId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getMenuItems() { + return menuItems; + } + + public void setMenuItems(String menuItems) { + this.menuItems = menuItems; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMenuConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMenuConfigMapper.java new file mode 100644 index 0000000..1b5c771 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMenuConfigMapper.java @@ -0,0 +1,35 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysUserMenuConfig; + +/** + * 用户左侧菜单配置 数据层 + * + * @author ruoyi + */ +public interface SysUserMenuConfigMapper { + + /** + * 根据用户ID查询配置 + * + * @param userId 用户ID + * @return 配置信息 + */ + SysUserMenuConfig selectByUserId(Long userId); + + /** + * 新增配置 + * + * @param config 配置信息 + * @return 结果 + */ + int insertConfig(SysUserMenuConfig config); + + /** + * 更新配置 + * + * @param config 配置信息 + * @return 结果 + */ + int updateConfig(SysUserMenuConfig config); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserMenuConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserMenuConfigService.java new file mode 100644 index 0000000..d3ccd59 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserMenuConfigService.java @@ -0,0 +1,30 @@ +package com.ruoyi.system.service; + +import com.ruoyi.system.domain.SysUserMenuConfig; + +/** + * 用户左侧菜单配置 服务层 + * + * @author ruoyi + */ +public interface ISysUserMenuConfigService { + + /** + * 根据当前登录用户ID查询配置 + * + * @param userId 用户ID + * @return 配置信息,无则返回 null + */ + SysUserMenuConfig selectByUserId(Long userId); + + /** + * 保存当前用户的菜单配置(有则更新,无则新增) + * + * @param userId 用户ID + * @param menuItems 菜单项JSON字符串 + * @param position 菜单位置 + * @param operator 操作人(createBy/updateBy) + * @return 结果 + */ + int saveConfig(Long userId, String menuItems, String position, String operator); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserMenuConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserMenuConfigServiceImpl.java new file mode 100644 index 0000000..367a20c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserMenuConfigServiceImpl.java @@ -0,0 +1,55 @@ +package com.ruoyi.system.service.impl; + +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserMenuConfig; +import com.ruoyi.system.mapper.SysUserMenuConfigMapper; +import com.ruoyi.system.service.ISysUserMenuConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 用户左侧菜单配置 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysUserMenuConfigServiceImpl implements ISysUserMenuConfigService { + + @Autowired + private SysUserMenuConfigMapper menuConfigMapper; + + @Override + public SysUserMenuConfig selectByUserId(Long userId) { + if (userId == null) { + return null; + } + return menuConfigMapper.selectByUserId(userId); + } + + @Override + public int saveConfig(Long userId, String menuItems, String position, String operator) { + if (userId == null) { + return 0; + } + SysUserMenuConfig existing = menuConfigMapper.selectByUserId(userId); + if (existing != null) { + existing.setMenuItems(menuItems); + if (StringUtils.isNotEmpty(position)) { + existing.setPosition(position); + } + if (StringUtils.isNotEmpty(operator)) { + existing.setUpdateBy(operator); + } + return menuConfigMapper.updateConfig(existing); + } else { + SysUserMenuConfig config = new SysUserMenuConfig(); + config.setUserId(userId); + config.setMenuItems(menuItems); + config.setPosition(StringUtils.isEmpty(position) ? "left" : position); + if (StringUtils.isNotEmpty(operator)) { + config.setCreateBy(operator); + } + return menuConfigMapper.insertConfig(config); + } + } +} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMenuConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMenuConfigMapper.xml new file mode 100644 index 0000000..b03f615 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMenuConfigMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + select config_id, user_id, menu_items, position, create_by, create_time, update_by, update_time, remark + from sys_user_menu_config + + + + + + insert into sys_user_menu_config ( + user_id, + menu_items, + position, + create_by, + remark, + create_time + ) values ( + #{userId}, + #{menuItems}, + #{position}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user_menu_config + + menu_items = #{menuItems}, + position = #{position}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + diff --git a/ruoyi-ui/src/api/system/userMenuConfig.js b/ruoyi-ui/src/api/system/userMenuConfig.js new file mode 100644 index 0000000..a87faba --- /dev/null +++ b/ruoyi-ui/src/api/system/userMenuConfig.js @@ -0,0 +1,23 @@ +import request from '@/utils/request' + +/** + * 获取当前用户的左侧菜单配置 + */ +export function getMenuConfig() { + return request({ + url: '/system/user/menuConfig', + method: 'get' + }) +} + +/** + * 保存当前用户的左侧菜单配置 + * @param {Object} data - { menuItems: string (JSON), position: string } + */ +export function saveMenuConfig(data) { + return request({ + url: '/system/user/menuConfig', + method: 'put', + data: data + }) +} diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 6781269..15f1d19 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -46,7 +46,7 @@
-
+
{{ scaleBarText }}
@@ -89,6 +89,15 @@ export default { type: String, default: 'airspace' // 'airspace' or 'ranging' }, + scaleConfig: { + type: Object, + default: () => ({ + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'km', + position: 'bottom-right' + }) + } }, watch: { drawDomClick: { @@ -99,6 +108,14 @@ export default { // this.initMap() } } + }, + scaleConfig: { + deep: true, + handler(newVal) { + if (newVal) { + this.updateScaleFromConfig(newVal) + } + } } }, data() { @@ -149,6 +166,10 @@ export default { // 比例尺(高德风格) scaleBarText: '--', scaleBarWidthPx: 80, + useCustomScale: false, + customScaleText: '', + currentScaleUnit: 'm', + isApplyingScale: false, // 定位相关 locateDialogVisible: false } @@ -174,6 +195,102 @@ export default { this.destroyViewer() }, methods: { + updateScaleFromConfig(config) { + if (!config || !this.viewer) return + + const { scaleNumerator, scaleDenominator, unit } = config + if (!scaleDenominator || scaleDenominator <= 0) return + + let displayValue = '' + let metersPerPixel = 0 + + const standardDPI = 96 + const pixelsPerCm = standardDPI * 0.393701 + + const scaleFactor = scaleDenominator / scaleNumerator + + switch (unit) { + case 'm': + displayValue = `1:${scaleDenominator}` + metersPerPixel = scaleFactor / pixelsPerCm + break + case 'km': + displayValue = `1:${scaleDenominator}` + metersPerPixel = (scaleFactor * 1000) / pixelsPerCm + break + default: + displayValue = `1:${scaleDenominator}` + metersPerPixel = scaleFactor / pixelsPerCm + } + + console.log('比例尺设置:', displayValue, '每像素米数:', metersPerPixel.toFixed(4)) + + this.isApplyingScale = true + this.useCustomScale = true + this.customScaleText = displayValue + this.currentScaleUnit = unit + + this.applyScaleToCamera(metersPerPixel) + this.updateScaleBar() + this.$forceUpdate() + + setTimeout(() => { + this.isApplyingScale = false + }, 1000) + }, + + applyScaleToCamera(metersPerPixel) { + if (!this.viewer || !this.viewer.camera) return + + const canvas = this.viewer.scene.canvas + const width = canvas.clientWidth + const height = canvas.clientHeight + + if (width <= 0 || height <= 0) return + + const camera = this.viewer.camera + const scene = this.viewer.scene + + if (scene.mode === Cesium.SceneMode.SCENE2D) { + const frustumWidth = width * metersPerPixel + camera.frustum.right = frustumWidth / 2 + camera.frustum.left = -frustumWidth / 2 + const frustumHeight = height * metersPerPixel + camera.frustum.top = frustumHeight / 2 + camera.frustum.bottom = -frustumHeight / 2 + } else { + const cameraPosition = camera.positionCartographic + const groundWidth = width * metersPerPixel + + const fov = camera.frustum.fov || Cesium.Math.PI_OVER_THREE + const aspectRatio = width / height + const verticalFov = fov / aspectRatio + + const targetHeight = (groundWidth / 2) / Math.tan(fov / 2) + + const destination = Cesium.Cartesian3.fromRadians( + cameraPosition.longitude, + cameraPosition.latitude, + targetHeight + ) + + camera.flyTo({ + destination: destination, + duration: 0.5, + orientation: { + heading: camera.heading, + pitch: camera.pitch, + roll: camera.roll + } + }) + } + }, + handleScaleClick() { + this.$emit('scale-click', { + useCustomScale: this.useCustomScale, + customScaleText: this.customScaleText + }) + }, preventContextMenu(e) { e.preventDefault(); }, @@ -2926,6 +3043,10 @@ export default { initScaleBar() { const that = this const update = () => { + if (!that.isApplyingScale) { + that.useCustomScale = false + that.customScaleText = '' + } that.updateScaleBar() } update() @@ -2966,9 +3087,10 @@ export default { if (leftCartesian && rightCartesian) { const rawMeters = Cesium.Cartesian3.distance(leftCartesian, rightCartesian) if (rawMeters > 0) { + const metersPerPx = rawMeters / barPx const niceMeters = this.niceScaleValue(rawMeters) const widthPx = Math.round((niceMeters / rawMeters) * barPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` + const text = this.formatScaleText(niceMeters) return { text, widthPx, niceMeters } } } @@ -2982,8 +3104,8 @@ export default { const rawMeters = metersPerPx * barPx const niceMeters = this.niceScaleValue(rawMeters) const widthPx = Math.round(niceMeters / metersPerPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` - return { text, widthPx: Math.min(120, Math.max(40, widthPx)), niceMeters } + const text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } // 最后备用:从 2D 相机视锥估算(正交宽度 -> 米) @@ -2994,12 +3116,31 @@ export default { const rawMeters = metersPerPx * barPx const niceMeters = this.niceScaleValue(rawMeters) const widthPx = Math.round(niceMeters / metersPerPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` - return { text, widthPx: Math.min(120, Math.max(40, widthPx)), niceMeters } + const text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } return null }, + /** 计算比例尺比例 */ + calculateScaleRatio(metersPerPx) { + const standardDPI = 96 + const pixelsPerCm = standardDPI * 0.393701 + const metersPerCm = metersPerPx * pixelsPerCm + let scaleRatio = 0 + if (this.currentScaleUnit === 'km') { + scaleRatio = Math.round(metersPerCm / 1000) + } else { + scaleRatio = Math.round(metersPerCm) + } + return scaleRatio + }, + /** 获取1厘米对应的像素数 */ + getPixelsPerCm() { + const standardDPI = 96 + const pixelsPerCm = standardDPI * 0.393701 + return pixelsPerCm + }, /** 将实际距离圆整为易读的刻度值(米) */ niceScaleValue(meters) { const candidates = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000] @@ -3009,14 +3150,29 @@ export default { } return best }, + /** 格式化比例尺文本 */ + formatScaleText(meters) { + if (meters >= 1000) { + const kmValue = (meters / 1000).toFixed(0) + return `${kmValue}km` + } else { + return `${meters}m` + } + }, updateScaleBar() { - const info = this.getScaleBarInfo() - if (info) { - this.scaleBarText = info.text - this.scaleBarWidthPx = Math.min(120, Math.max(40, info.widthPx)) + if (this.useCustomScale && this.customScaleText) { + this.scaleBarText = `${this.customScaleText}${this.currentScaleUnit}` + const pixelsPerCm = this.getPixelsPerCm() + this.scaleBarWidthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) } else { - this.scaleBarText = '--' - this.scaleBarWidthPx = 80 + const info = this.getScaleBarInfo() + if (info) { + this.scaleBarText = info.text + this.scaleBarWidthPx = Math.min(120, Math.max(40, info.widthPx)) + } else { + this.scaleBarText = '--' + this.scaleBarWidthPx = 80 + } } this.$nextTick() }, diff --git a/ruoyi-ui/src/views/childRoom/TopHeader.vue b/ruoyi-ui/src/views/childRoom/TopHeader.vue index b04d5d2..2b027a7 100644 --- a/ruoyi-ui/src/views/childRoom/TopHeader.vue +++ b/ruoyi-ui/src/views/childRoom/TopHeader.vue @@ -334,6 +334,14 @@ export default { isIconEditMode: { type: Boolean, default: false + }, + currentScaleConfig: { + type: Object, + default: () => ({ + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }) } }, data() { @@ -366,6 +374,16 @@ export default { ] } }, + watch: { + currentScaleConfig: { + deep: true, + handler(newVal) { + if (newVal) { + this.currentScale = { ...newVal } + } + } + } + }, methods: { selectTopNav(item) { this.$emit('select-nav', item) diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 57978ad..5f32161 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -6,8 +6,10 @@ @open-route-dialog="handleOpenRouteEdit" />
@@ -67,6 +69,7 @@ :can-set-k-time="canSetKTime" :user-avatar="userAvatar" :is-icon-edit-mode="isIconEditMode" + :current-scale-config="scaleConfig" @select-nav="selectTopNav" @set-k-time="openKTimeSetDialog" @save-plan="savePlan" @@ -269,6 +272,7 @@ v-model="showScaleDialog" :scale="currentScale" @save="saveScale" + @unit-change="handleScaleUnitChange" /> @@ -368,7 +372,16 @@ export default { showPowerZoneDialog: false, currentPowerZone: {}, showScaleDialog: false, - currentScale: {}, + currentScale: { + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }, + scaleConfig: { + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }, showExternalParamsDialog: false, currentExternalParams: {}, showPageLayoutDialog: false, @@ -1395,10 +1408,36 @@ export default { saveScale(scale) { console.log('保存比例尺:', scale) + this.scaleConfig = { + scaleNumerator: scale.scaleNumerator, + scaleDenominator: scale.scaleDenominator, + unit: scale.unit + } + + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.updateScaleFromConfig(this.scaleConfig) + } + const scaleText = `${scale.scaleNumerator}:${scale.scaleDenominator}` this.$message.success(`比例尺 "${scaleText}" 保存成功`); }, + handleScaleClick(scaleInfo) { + this.currentScale = { + scaleNumerator: this.scaleConfig.scaleNumerator, + scaleDenominator: this.scaleConfig.scaleDenominator, + unit: this.scaleConfig.unit + } + this.showScaleDialog = true + }, + + handleScaleUnitChange(unit) { + this.scaleConfig.unit = unit + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.currentScaleUnit = unit + } + }, + // 地图下拉菜单方法 loadTerrain() { this.$message.success('加载/切换地形'); diff --git a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue index 4015c8e..b6650ba 100644 --- a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue +++ b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue @@ -30,17 +30,6 @@ - - - - - - - - - - - @@ -71,9 +60,8 @@ export default { return { formData: { scaleNumerator: 1, - scaleDenominator: 1000, - unit: 'km', - position: 'bottom-right' + scaleDenominator: 20, + unit: 'km' }, rules: { scaleNumerator: [ @@ -84,9 +72,6 @@ export default { ], unit: [ { required: true, message: '请选择单位', trigger: 'change' } - ], - position: [ - { required: true, message: '请选择显示位置', trigger: 'change' } ] } }; @@ -101,15 +86,19 @@ export default { if (this.value && newVal) { this.initFormData(); } + }, + 'formData.unit'(newVal) { + if (this.value) { + this.$emit('unit-change', newVal); + } } }, methods: { initFormData() { this.formData = { scaleNumerator: this.scale.scaleNumerator || 1, - scaleDenominator: this.scale.scaleDenominator || 1000, - unit: this.scale.unit || 'km', - position: this.scale.position || 'bottom-right' + scaleDenominator: this.scale.scaleDenominator || 20, + unit: this.scale.unit || 'km' }; }, closeDialog() { @@ -151,7 +140,7 @@ export default { border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); width: 90%; - max-width: 500px; + max-width: 400px; max-height: 90vh; overflow-y: auto; animation: dialog-fade-in 0.3s ease; @@ -173,7 +162,7 @@ export default { display: flex; align-items: center; justify-content: space-between; - padding: 16px 20px; + padding: 12px 16px; border-bottom: 1px solid #e8e8e8; } @@ -196,7 +185,17 @@ export default { } .dialog-body { - padding: 20px; + padding: 16px; +} + +.quick-scale-buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.quick-scale-buttons .el-button { + margin: 0; } .scale-inputs {