开发一个前后端分离的webgis城市共享单车投放管理系统(1)

380 阅读4分钟

文章同步更新于我的个人博客:松果猿的博客,欢迎访问获取更多技术分享。

同时,您也可以关注我的微信公众号:松果猿的代码工坊,获取最新文章推送和编程技巧。

项目初始化

这个项目严格意义上来说是一个课程作业,不过老师还让我们用html+juery来写,诶呀,实在在原始了好吧,不是说这种方式不好(跟着老师的方法写了一遍,可能是我react vue写多了,怎么感觉写着这么别扭呢?),而是这已经不是主流了,下面我就直接用vite+vue3+springboot+postgresql重构一下这个项目吧

我之前那一篇文章已经配置了初始化项目:Vite初始化Vue3+Typescrpt项目,可以遵循我的方法配置一下项目。

配置好项目后,我们就可以开始敲了

首先初步想一下大致UI框架:

UI

项目框架

首先新建Header组件

@/components/Header.vue

<template>
  <div class="header">
    <div class="logo">
      <img src="../assets/img/logo.png" alt="logo" />
    </div>
    <div class="navigation">
      <ul>
        <li v-for="item in nav_list">{{ item.name }}</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import {} from "vue";
const nav_list = [
  { name: "查询投放区域" },
  { name: "增设投放区域" },
  { name: "单车定位查询" },
  { name: "单车热点分析" },
];
</script>
<style lang="scss" scoped>
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  height: 64px;
  background: rgba(8, 10, 18, 0.72); 
  backdrop-filter: blur(12px);  
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);

  .logo {
    img {
      height:55px;
      transition: all 0.3s ease;
      
      &:hover {
        transform: scale(1.05);
      }
    }
  }

  .navigation {
    margin-left: 40px;
    
    ul {
      display: flex;
      list-style: none;
      margin: 0;
      padding: 0;
      gap: 8px; 
      
      li {
        padding: 8px 16px;
        font-size: 18px;
        color: #ffffff;
        font-weight: 500;
        cursor: pointer;
        border-radius: 8px;
        transition: all 0.3s ease;
        
        &:hover {
          background: rgb(255, 255, 255);
          color: #007AFF;  
        }
        
        &:active {
          background: rgba(75, 75, 75, 0.1);
          transform: scale(0.98);
        }
      }
    }
  }
}
</style>

在根组件App.vue引入

<template>
  <div class="app">
    <Header></Header>
  </div>
</template>

<script setup lang="ts">
import Header from "@/components/Header.vue";
</script>

<style scoped>  
</style>

大致结构已经出现了

image-20241117143745471

Openlayers使用天地图

开始将openlayers引入吧,不过在此之前我们需要理解一下Openlayers相关基础:

image-20241117151238944

Openlayers将整个地图看作是一个容器对象(Map),其核心组件包括地图图层对象(Layer),对应图层的数据源对象(Source)与矢量图层样式对象(Style),地图展示相关的地图试图对象(View),还有与地图投影相关的Projection对象,除此之外容器中还有一些特别的层,如Overlayers表示叠加在地图之上的业务图层,内置的地图控件对象Controls。

在view文件夹下新建Home.vue,在component文件夹下新建OlMap.vue

@/views/Home.vue

<template>
  <div class="home">
    <OlMap></OlMap>
  </div>
</template>

<script setup>
import OlMap from "@/components/OlMap.vue";
</script>
<style scoped>
.home {
  width: 100%;
  height: 100vh;
  position: relative;
  overflow: hidden;
}
</style>

@/components/OlMap.vue

<template>
  <div id="map"></div>
</template>

<script setup>
import { onMounted, ref } from "vue";
import { Map, View } from "ol";
import { transform } from "ol/proj";
import { Tile } from "ol/layer";
import XYZ from "ol/source/XYZ";

const map = ref(null);
const token = "换成申请的天地图key";
// 初始化地图
const initMap = () => {
  const view = new View({
    center: transform([118.65, 32.09], "EPSG:4326", "EPSG:3857"),
    zoom: 10,
  });

  // 创建天地图矢量底图和标签图层
  var layerTianDi = new Tile({
    source: new XYZ({
      url: getLayerUrlByData("street", 4326, token),
      projection: "EPSG:4326",
      wrapX: true,
      crossOrigin: "anonymous",
    }),
  });

  var layerTianDiLabel = new Tile({
    source: new XYZ({
      url: getLayerUrlByData("street_label", 4326, token),
      projection: "EPSG:4326",
      wrapX: true,
      crossOrigin: "anonymous",
    }),
  });

  // 创建地图实例
  map.value = new Map({
    target: "map",
    layers: [layerTianDi, layerTianDiLabel],
    view,
  });
};

// 根据图层类型生成相应的URL
function getLayerUrlByData(type, wkid, token) {
  let url = "http://t{1-7}.tianditu.com/DataServer?";
  let layerId;

  switch (type) {
    case "image":
      layerId = "img_";
      break;
    case "label":
      layerId = "cia_";
      break;
    case "street":
      layerId = "vec_";
      break;
    case "street_label":
      layerId = "cva_";
      break;
    default:
      throw new Error("Unsupported layer type");
  }

  const tileMatrixSetId = wkid === 4326 ? "c" : "w";
  return `${url}T=${layerId}${tileMatrixSetId}&x={x}&y={y}&l={z}&tk=${token}`;
}

onMounted(() => {
  initMap();
});
</script>

<style lang="scss" scoped>
#map {
  width: 100%;
  height: 100%;
}
</style>

地图已经成功显示出来了(我用的是天地图的数据源,需要去国家地理信息公共服务平台申请key,天地图同时提供 WMTS 和 XYZ(或其他格式)的服务,我这里使用XYZ)

image-20241117153105198

添加菜单栏

下面我们给地图右上角添加一个菜单栏,咱们使用Element-Plus现成的组件,构建更快

新建@/components/MapControl.vue

<template>
  <el-menu
    class="map-control-menu"
    mode="horizontal"
    :ellipsis="false"
    :popper-offset="12"
    @select="handleSelect"
  >
    <!-- 定位菜单 -->
    <el-sub-menu index="Draw">
      <template #title>
        <el-icon><EditPen /></el-icon>
        <span>绘制</span>
      </template>
      <el-menu-item
        v-for="item in menuItems.Draw"
        :key="item.id"
        :index="`Draw-${item.id}`"
      >
        {{ item.name }}
      </el-menu-item>
    </el-sub-menu>

    <!-- 图层菜单 -->
    <el-sub-menu index="layers">
      <template #title>
        <el-icon><MapLocation /></el-icon>
        <span>图层</span>
      </template>
      <el-menu-item
        v-for="item in menuItems.layers"
        :key="item.id"
        :index="`layers-${item.id}`"
      >
        {{ item.name }}
      </el-menu-item>
    </el-sub-menu>

    <!-- 工具菜单 -->
    <el-sub-menu index="tools">
      <template #title>
        <el-icon><Tools /></el-icon>
        <span>工具</span>
      </template>
      <el-menu-item
        v-for="item in menuItems.tools"
        :key="item.id"
        :index="`tools-${item.id}`"
      >
        {{ item.name }}
      </el-menu-item>
    </el-sub-menu>
  </el-menu>
</template>

<script setup>
import {EditPen, MapLocation, Tools } from "@element-plus/icons-vue";

const menuItems = {
  Draw: [
    { id: "drawPoint", name: "绘制点" },
    { id: "drawLine", name: "绘制线" },
    { id: "drawPolygon", name: "绘制面" },
  ],
  layers: [
    { id: "satellite", name: "影像图" },
    { id: "street", name: "街道图" },
    { id: "terrain", name: "地形图" },
  ],
  tools: [
    { id: "measure", name: "测量" },
    { id: "draw", name: "绘制" },
    { id: "analysis", name: "分析" },
  ],
};

</script>

<style lang="scss" scoped>
.map-control-menu {
  position: absolute;
  top: 20px;
  right: 20px;
  background: rgba(255, 255, 255, 0.85);
  border-radius: 10px;
  padding: 4px;
  overflow: visible;
  border: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
</style>

Home.vue引入菜单栏组件

image-20241117174225213

菜单栏成功显示在地图上

image-20241117174419009

这样我们就把主要的UI框架实现了。

项目地址:github.com/songguo1/Sh…