(依旧还是叠甲,我也是菜鸟,悉知我的功力还很浅薄,如果有哪里做的不行欢迎批评指正,大部分代码都是借助AI辅助编写的,可能会有些问题,遇到可留言指正)
        Day 4:

终于要开始进行前端搭建了QAQ,前端框架很多,我们选择vue3(绝对不是因为我只会这个),关于webgis方面的我们就选择leaflet,(下次整openlayers),先梳理一下思路,前端 搭建大致以下几个步骤:

话不多说我们直接进入正题

第一步:安装各种插件

1.node.js

1.下载

下载地址:Node.js — 下载 Node.js®  下个吻合电脑版本号,下完一路next就行,下完 win+r 输入cmd打开控制台 

能显示出这俩就算完成了

2.环境配置

说明:这里的环境配置主要配置的是npm安装的全局模块所在的路径,以及缓存cache的路径,之所以要配置,是因为以后在执行类似:npm install express [-g] (后面的可选参数-g,g代表global全局安装的意思)的安装语句时,会将安装的模块安装到【C:\Users\用户名\AppData\Roaming\npm】路径中,占C盘空间。
例如:我希望将全模块所在路径和缓存路径放在我node.js安装的文件夹中,则在我安装的文件夹【D:\Develop\nodejs】下创建两个文件夹【node_global】及【node_cache】如下图:

创建完两个空文件夹之后,打开cmd命令窗口,输入:

路径就是你创的文件夹的路径

npm config set prefix "D:\Develop\nodejs\node_global"
npm config set cache "D:\Develop\nodejs\node_cache"

接下来设置环境变量,关闭cmd窗口,“我的电脑”-右键-“属性”-“高级系统设置”-“高级”-“环境变量”

进入环境变量对话框,在【系统变量】下新建【NODE_PATH】,输入【D:\Develop\nodejs\node_global\node_modules】,将【用户变量】下的【Path】修改为【D:\Develop\nodejs\node_global】

配置完后,安装个module测试下,我们就安装最常用的express模块,打开cmd窗口,
输入如下命令进行模块的全局安装:

npm install express -g     # -g是全局安装的意思

ok,结束

2.Vite(替代webpack 和 vue-cli)

1.切换路径

在我们的工程文件夹下建一个git文件夹,cmd切换到 c:\Users\zhou0\Desktop\MyMap\git 路径下,我把前端工程放在这里。

输入:

cd c:\Users\zhou0\Desktop\MyMap

2.全局安装vite

输入:

npm install -g vite@8.0.10

到此准备工作就差不多了,我们就可以开始搭建我们的前端。

第二步:前端搭建

1.vite脚手架新建项目

在cmd中输入:

npm create vite@latest vue-gismap -- --template vue

意思需要安装自带的内些页面吗,我们直接y

项目名字:vue-gismap

前端框架,你猜选啥,肯定是vue啊

语言就选js吧ts有点麻烦

用npm下载然后运行吗?直接回车

我们按住ctrl+左键点击这个地址打开我们的初始页面

就长这样,算是完成了,我们开始下一步

2. 安装element plus (vue3不支持element)

在终端里输入:

normalize是icon图标

npm install element-plus @element-plus/icons-vue
npm install normalize.css  
npm install vue-router  //安装路由

3. 开始布局

1. 准备工作

样式效果一样一样来,先做一下页面布局。大概长这个样子:顶部三个tab标签,历史地图、专题地图、帮助文档;左侧一栏树状复选框,用以选择朝代,中间部分是地图,右侧是一个可隐藏的表,用以显示属性。

布局采用了layout+container,路由用的是router,左侧的树状结构是tree控件,右侧点击“打开嵌套表格的drawer”,弹出的是drawer控件,内嵌table控件。

文件结构如下图所示,src文件夹下有components、js、router三个文件夹。安装完vue脚手架,在src文件夹下会生成一些默认的文件夹和组件,不需要的直接删掉就可以,右键——new file/new folder,新建需要的文件和文件夹即可。

Vite 项目初始 src 下只有 assets/components/ 以及 App.vuemain.js。我们需要手动创建:

  • src/router/index.js

  • src/js/treedata.js

  • src/components/Header.vueHistoryMap.vueSpecialMap.vueHelperDocument.vue

在vite.config.js里面我们弄一些配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)) // 路径别名,便于引用
    }
  }
})

2. main.ts

这个文件,主要是把用到的一些依赖导入,这些依赖都在文件夹node_modules下。

原教程用的vue2

Vue3 的入口写法完全不同,没有 new Vue(),而是通过 createApp 创建应用。Element Plus 的引入路径也变了,且全局注册图标的方式不同。完整改写为:

import { createApp } from 'vue'             // ▶ Vue 3 使用 createApp
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'        // ▶ 样式路径变化
import 'normalize.css'
// ▶ 不再使用 vue-awesome,改用 element-plus 图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import router from './router'

const app = createApp(App)

// 全局注册所有图标,方便在模板中直接使用 <el-icon><Menu /></el-icon>
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

app.use(ElementPlus)
app.use(router)
app.mount('#app')

3. App.vue

因为浏览器自带的滚动条太丑,所以在这里更改了全局的滚动条。<router-view></router-view>引入的是路由视图。

<template>
  <div id="app">
    <el-container>
      <!-- 两个容器形成上下结构 -->
      <el-header class="header">
        <Header />
      </el-header>
      <el-container>
        <!-- 两个容器形成上下结构 -->
        <el-main class="el-main">
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script setup>//使用语法糖引入 Header组件
import Header from '@/components/Header.vue'  // 引入 Header组件,之后直接就能在template中使用了

</script>

<style>
/* 样式 */
.header {
  background-color: #c00000;
  color: #fff;
  line-height: 60px;
}

.el-main {
  background-color: #E9EEF3;
  color: #333;
  text-align: center;
  line-height: 160px;
  height: calc(100vh - 60px);   //地图部分高度和视口一样大
}

/*更改全局的滚动条样式*/
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
  background-color: #fff;
}

::-webkit-scrollbar-thumb {
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
  background-color: rgba(0, 0, 0, .1);
}
</style>

4.Header.vue

这个就是主页面菜单栏,历史地图、专题地图、帮助文档。<el-row><el_col>用layout进行了一个布局。<el-menu>引入菜单。<router-link>引入路由,点击跳转页面。

以下是Header.vue 的代码

<template>
  <el-row :gutter="20">
    <el-col :span="4">
      <div>中国历史地图</div>
    </el-col>
    <el-col :span="16">
      <el-menu class="el-menu-demo" mode="horizontal" background-color="#c00000" text-color="#fff"
        active-text-color="#000">
        <el-menu-item index="1">
          <span><router-link to="/">历史地理</router-link></span>
        </el-menu-item>
        <el-menu-item index="2">
          <span><router-link to="/specialmap">专题地图</router-link></span>
        </el-menu-item>
        <el-menu-item index="3">
          <span><router-link to="/helperdocument">帮助文档</router-link></span>
        </el-menu-item>
      </el-menu>
    </el-col>
  </el-row>
</template>

<style>
.el-row {
  /* margin-top:10px;*/
  margin-bottom: 10px;

  &:last-child {
    margin-bottom: 0;
  }
}

.el-col {
  border-radius: 4px;
}

.bg-purple {
  background: #d3dce6;
}

.grid-content {
  border-radius: 4px;
  min-height: 36px;
}

/* 去掉菜单栏底部白边 */
.el-menu--horizontal {
  border-bottom: none !important;
}

.el-menu-item {
  border-bottom: none !important;
}

.el-header {
  padding: 0;
}
</style>

5. router/index.js

路由信息放在这个文件中。Path是url路径,是一个相对路径,component是组件页面。例如点击Header.vue中的帮助文档,就会跳转到HelperDocument.vue页面,url: http://localhost:8080/#/helperdocument

  • Vue Router 4(对应 Vue3)的导入方式改变:import { createRouter, createWebHashHistory } from 'vue-router'

  • 不再通过 new Router() 创建实例,而是使用 createRouter

  • 路由历史模式保留 hash 模式,使用 createWebHashHistory

  • 组件导入建议加 .vue 后缀(Vite 要求)。

import { createRouter, createWebHashHistory } from 'vue-router'  // ▶ Vue Router 4
import HistoryMap from '@/components/HistoryMap.vue'
import SpecialMap from '@/components/SpecialMap.vue'
import HelperDocument from '@/components/HelperDocument.vue'

const routes = [
  { path: '/', name: 'historymap', component: HistoryMap },
  { path: '/specialmap', name: 'specialmap', component: SpecialMap },
  { path: '/helperdocument', name: 'helperdocument', component: HelperDocument }
]

const router = createRouter({                     // ▶ 新式创建
  history: createWebHashHistory(),                // hash 模式,保持 URL #/xxx 风格
  routes
})

export default router

6. HistoryMap.vue

这里主要两个控件,一个是<el-tree>,它的数据单独放在了js/treedata.js中了。一个是<el-drawer>,数据在<script>的gridData中。methods 中有一个方法changecheck(e,data),e是当前选中的eltree节点,这个方法的用处是,对于复选框状态来说,父节点直接影响子节点。

老师教程Element Plus 中 el-drawer.sync 修饰符不再支持,需改用 v-model。将 :visible.sync="table" 改为 v-model="table"

<template>
  <div>
    <!-- 左侧树结构容器:白色背景、高度75%屏幕、宽度200px、左浮动 -->
    <div style="background-color: #fff; height: 75vh;width: 200px;float:left;">
      <!-- Element 滚动条组件,让树内容太多时可以滚动 -->
      <el-scrollbar style="height: 100%;">
        <!-- 
          el-tree:树形控件
          :data="treedata"    绑定树的数据
          show-checkbox       显示复选框
          node-key="id"       每个节点唯一标识用 id
          :props="defaultProps"  配置子节点、显示文字用哪个字段
          @check="changecheck"   勾选/取消勾选时触发的方法
        -->
        <el-tree :data="treedata" show-checkbox node-key="id" :props="defaultProps" @check="changecheck"></el-tree>
      </el-scrollbar>
    </div>

    <!-- 
      el-drawer:右侧抽屉组件
      v-model="table"      控制抽屉显示/隐藏(true显示/false隐藏)
      direction="rtl"      从右往左弹出
      size="20%"           抽屉宽度占屏幕20%
    -->
    <el-drawer title="属性" v-model="table" direction="rtl" size="20%">
      <el-table :data="gridData" height=85vh border>
        <!-- 表格列:field 字段名 -->
        <el-table-column property="field" label="字段" width="100"></el-table-column>
        <!-- 表格列:value 字段值 -->
        <el-table-column property="value" label="值" width="200"></el-table-column>
      </el-table>
    </el-drawer>

    <!-- 按钮:点击让 table = true → 打开抽屉 -->
    <el-button type="text" @click="table = true">打开嵌套表格的 Drawer</el-button>
  </div>
</template>

<script>
// 导入外部静态树数据(写死的数据)
import { treedata } from '../js/treedata.js'

export default {
  // 组件数据
  data() {
    return {
      treedata: treedata,        // 树的数据
      defaultProps: {            // 树的配置
        children: 'children',    // 子节点用 children 字段
        label: 'label'           // 显示文字用 label 字段
      },
      table: false,              // 控制右侧抽屉显示隐藏

      // ======================
      // 表格数据:纯写死数据
      // 不管点哪个树节点,都只显示这些内容
      // ======================
      gridData: [
        { field: 'name_py', value: 'Baode Zhou' },
        { field: 'name_ch', value: '保德州' },
        { field: 'name_ft', value: '保德州' },
        { field: 'pres_loc', value: '山西保德县城西侧' },
        { field: 'type_ch', value: '州' },
        { field: 'lev_rank', value: '6' },
        { field: 'beg_yr', value: 1376 },
        { field: 'beg_rule', value: '4' },
        { field: 'end_yr', value: 1911 },
        { field: 'geo_src', value: 'FROM_AC' },
        { field: 'compiler', value: '' },
        { field: 'gecomplr', value: '' },
        { field: 'checker', value: '' },
        { field: 'ent_date', value: '' },
        { field: 'beg_chg_ty', value: '更名' },
        { field: 'end_chg_ty', value: '数据下限' }
      ]
    }
  },

  // 方法集合
  methods: {
    // 树节点勾选时触发
    changecheck(e, data) {
      // 如果是 1级节点
      if (e['level'] === 1) {
        e['ischeck'] = !e['ischeck']; // 切换自身选中状态

        // 循环所有二级子节点 → 跟着选中/取消
        for (var i = 0; i < e['children'].length; i++) {
          e['children'][i]['ischeck'] = e['ischeck'];

          // 循环三级子节点 → 跟着选中/取消
          for (var j = 0; j < e['children'][i]['children'].length; j++) {
            e['children'][i]['children'][j] = e['ischeck'];
          }
        }
      }
      // 如果是 2级节点
      else if (e['level'] === 2) {
        e['ischeck'] = !e['ischeck'];
        // 让下面3个子节点跟着选中/取消
        e['children'][0]['ischeck'] = e['ischeck'];
        e['children'][1]['ischeck'] = e['ischeck'];
        e['children'][2]['ischeck'] = e['ischeck'];
      }
      // 3级节点(叶子节点)
      else {
        e['ischeck'] = !e['ischeck']; // 只切换自己
      }

      console.log(e); // 打印当前节点信息
    }
  }
};
</script>

7. treedata.js

eltree的数据放在这里。

export const treedata = [
  {
    id: 0,
    label: "全部内容",
    ischeck: false,
    level: 1,
    timerange: [-5000, 1840],
    children: [
      {
        id: 1,
        label: "商周",
        ischeck: false,
        level: 2,
        timerange: [-5000, -221],
        children: [
          { id: 2, label: "cnty点", ischeck: false, level: 3, timerange: [-5000, -221] },
          { id: 3, label: "pref点", ischeck: false, level: 3, timerange: [-5000, -221] },
          { id: 4, label: "pref面", ischeck: false, level: 3, timerange: [-5000, -221] }
        ]
      },
      {
        id: 5,
        label: "秦",
        ischeck: false,
        level: 2,
        timerange: [-220, -202],
        children: [
          { id: 6, label: "cnty点", ischeck: false, level: 3, timerange: [-220, -202] },
          { id: 7, label: "pref点", ischeck: false, level: 3, timerange: [-220, -202] },
          { id: 8, label: "pref面", ischeck: false, level: 3, timerange: [-220, -202] }
        ]
      },
      {
        id: 9,
        label: "汉",
        ischeck: false,
        level: 2,
        timerange: [-201, 220],
        children: [
          { id: 10, label: "cnty点", ischeck: false, level: 3, timerange: [-201, 220] },
          { id: 11, label: "pref点", ischeck: false, level: 3, timerange: [-201, 220] },
          { id: 12, label: "pref面", ischeck: false, level: 3, timerange: [-201, 220] }
        ]
      },
      {
        id: 13,
        label: "晋三国",
        ischeck: false,
        level: 2,
        timerange: [221, 589],
        children: [
          { id: 14, label: "cnty点", ischeck: false, level: 3, timerange: [221, 589] },
          { id: 15, label: "pref点", ischeck: false, level: 3, timerange: [221, 589] },
          { id: 16, label: "pref面", ischeck: false, level: 3, timerange: [221, 589] }
        ]
      },
      {
        id: 17,
        label: "隋唐",
        ischeck: false,
        level: 2,
        timerange: [590, 907],
        children: [
          { id: 18, label: "cnty点", ischeck: false, level: 3, timerange: [590, 907] },
          { id: 19, label: "pref点", ischeck: false, level: 3, timerange: [590, 907] },
          { id: 20, label: "pref面", ischeck: false, level: 3, timerange: [590, 907] }
        ]
      },
      {
        id: 21,
        label: "五代十国",
        ischeck: false,
        level: 2,
        timerange: [908, 960],
        children: [
          { id: 22, label: "cnty点", ischeck: false, level: 3, timerange: [908, 960] },
          { id: 23, label: "pref点", ischeck: false, level: 3, timerange: [908, 960] },
          { id: 24, label: "pref面", ischeck: false, level: 3, timerange: [908, 960] }
        ]
      },
      {
        id: 25,
        label: "宋",
        ischeck: false,
        level: 2,
        timerange: [961, 1276],
        children: [
          { id: 26, label: "cnty点", ischeck: false, level: 3, timerange: [961, 1276] },
          { id: 27, label: "pref点", ischeck: false, level: 3, timerange: [961, 1276] },
          { id: 28, label: "pref面", ischeck: false, level: 3, timerange: [961, 1276] }
        ]
      },
      {
        id: 29,
        label: "元",
        ischeck: false,
        level: 2,
        timerange: [1277, 1368],
        children: [
          { id: 30, label: "cnty点", ischeck: false, level: 3, timerange: [1277, 1368] },
          { id: 31, label: "pref点", ischeck: false, level: 3, timerange: [1277, 1368] },
          { id: 32, label: "pref面", ischeck: false, level: 3, timerange: [1277, 1368] }
        ]
      },
      {
        id: 33,
        label: "明",
        ischeck: false,
        level: 2,
        timerange: [1369, 1644],
        children: [
          { id: 34, label: "cnty点", ischeck: false, level: 3, timerange: [1369, 1644] },
          { id: 35, label: "pref点", ischeck: false, level: 3, timerange: [1369, 1644] },
          { id: 36, label: "pref面", ischeck: false, level: 3, timerange: [1369, 1644] }
        ]
      },
      {
        id: 37,
        label: "清",
        ischeck: false,
        level: 2,
        timerange: [1645, 1840],
        children: [
          { id: 38, label: "cnty点", ischeck: false, level: 3, timerange: [1645, 1840] },
          { id: 39, label: "pref点", ischeck: false, level: 3, timerange: [1645, 1840] },
          { id: 40, label: "pref面", ischeck: false, level: 3, timerange: [1645, 1840] }
        ]
      }
    ]
  }
];

8. SpecialMap和HelperDocument

SpecialMap

<template>
  <div>专题地图</div>
</template>

HelperDocument

<template>
  <div>帮助文档</div>
</template>

这两个预留,可能以后会用专题地图这个页面做一个三维的地图(老师好像到最后也没做,但是我可以给老师填个坑)。

4. 整合leaflet

        我们大概要做这样的地图底图,腾讯底图,上面覆盖物有点point和面polygon,点击覆盖物会出现触发事件。页面长成这个样子,因为仅作示例用,所以写在了helpdocument上面,除了地图外,覆盖物有一个点、一个标签、一个面,右侧是chrome浏览器的开发者工具(crtl+shift+i),触发事件在工具上(console)打印了日志(老师原话加老师原图)

 1. 安装leaflet模块

首先要安装leaflet模块。 Ctrl+c,退出项目运行。 然后在cmd运行:

npm install leaflet

2.  加载腾讯底图

1.瓦片原理

Leaflet加载底图以栅格瓦片的形式加载。官方示例是这样的:

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

其中{}中的s是源,z是比例尺,xy是瓦片的坐标,加载的是openstreetmap。地图瓦片是比较基础的概念,为了提升性能,将地图按照比例尺逐级切成瓦片,调用的时候,根据比例尺和网页尺寸调用需要调用的瓦片。关于地图瓦片,开放地理信息空间联盟OGC专门有规范WMTS对其进行阐述,国内的高德、腾讯地图瓦片切分方式,完全按照WMTS来,但是谷歌和mapbox、OSM等稍有变化。因为leaflet默认加载的是谷歌地图这样的瓦片,所以要加载腾讯地图,还需要对瓦片函数进行一个转换,才能正确加载。

2.程序源码

新建一个 src/js/txTileLayer.js 文件,放瓦片函数。

// 自定义地图瓦片图层:继承 Leaflet 原生 L.TileLayer
// 作用:自定义瓦片地图的 URL 拼接规则(让地图瓦片加载更灵活)
export const TXTileLayer = L.TileLayer.extend({
  // 重写【获取瓦片 URL】的核心方法
  getTileUrl: function (tilePoint) {

    // 定义变量:存储最终要拼接进 URL 的参数(x,y,z等)
    let urlArgs = null

    // 从配置项里取出用户传入的 getUrlArgs 方法
    let getUrlArgs = this.options.getUrlArgs

    // ======================================
    // 如果外部传入了自定义 getUrlArgs 函数
    // 就用外部自定义的方式生成 url 参数
    // ======================================
    if (getUrlArgs) {
      urlArgs = getUrlArgs(tilePoint)
    }
    // ======================================
    // 否则使用默认规则:z/x/y
    // 这是标准瓦片地图的格式
    // ======================================
    else {
      urlArgs = {
        z: tilePoint.z,   // 地图层级
        x: tilePoint.x,   // 瓦片横向坐标
        y: tilePoint.y    // 瓦片纵向坐标
      }
    }
    // ======================================
    // 拼接最终瓦片 URL 并返回
    // 1. L.Util.template:把参数填充到 url 模板里
    // 2. L.extend:合并参数(urlArgs + 配置项 + 子域名)
    // 3. this._getSubdomain:获取子域名(用于多域名加速加载)
    // ======================================
    return L.Util.template(this._url, L.extend(urlArgs, this.options, {
      s: this._getSubdomain(tilePoint)
    }))
  }
})

src/icon 中放一个 dian.png,这个是点的图标(推荐去阿里矢量图标库下iconfont-阿里巴巴矢量图标库)。

剩下的所有都写在了 src/components/HelperDocument.vue 中。(测试的一个地图)

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

<script>
import L from 'leaflet'                                        // ▶ 引入 Leaflet 主对象
import 'leaflet/dist/leaflet.css'                              // 样式不变
import { TXTileLayer } from '../js/txTileLayer.js'
import dianIcon from '../icon/dian.png'                        // ▶ 替换 require('../icon/dian.png')
import markerShadow from 'leaflet/dist/images/marker-shadow.png'  // ▶ 替换 require('leaflet/dist/images/marker-shadow.png')

export default {
  data() {
    return {
      map: null
    }
  },
  methods: {
    initLeaflet() {
      this.map = L.map('map', {
        center: [39.916527, 116.397128],
        zoom: 10,
        maxZoom: 23,
        minZoom: 3
      })
      //按照新定义瓦片规则加载底图
      let url = 'http://rt1.map.gtimg.com/realtimerender/?z={z}&x={x}&y={y}&type=vector&style=1&v=1.1.1'
      let getUrlArgs = function(tilePoint) {
        return {
          z: tilePoint.z,
          x: tilePoint.x,
          y: Math.pow(2, tilePoint.z) - 1 - tilePoint.y
        }
      }
      let options = {
        subdomain: '012',
        getUrlArgs: getUrlArgs
      }
      const txMap = new TXTileLayer(url, options)
      txMap.addTo(this.map)

      //marker
      var myDivIcon = L.divIcon({ className: 'my-div-icon', html: '这是一个测试用的marker', iconSize: 150 });
      var marker = L.marker([39.916527, 116.397128], { icon: myDivIcon }).addTo(this.map);
      marker.on('click', function(e) {
        console.log(e);
        alert('我是Marker。');
      })

      //point
      var myIcon = L.icon({
        iconUrl: dianIcon,                // ▶ 直接使用导入的图片
        iconSize: [30, 30],
        iconAnchor: [15, 15],
        popupAnchor: [40, 0],
        shadowUrl: markerShadow,          // ▶ 使用导入的阴影
        shadowSize: [30, 30],
        shadowAnchor: [15, 15]
      });
      var point = L.marker([39.916527, 116.397128], { icon: myIcon, title: '这是一个测试用的point' }).addTo(this.map);
      point.on('click', function(e) {
        console.log(e);
        alert('我是point。');
      })

      //polygon
      var polygon = L.polygon([
        [[40.0, 116.4],[40.1, 116.4],[40.1, 116.5],[40.0, 116.5]], // 外部
        [[40.05, 116.45],[40.06, 116.45],[40.06, 116.46],[40.05, 116.46]] // 孔洞
      ], {
        color: 'green',
        fillColor: '#f03',
        fillOpacity: 0.5
      }).addTo(this.map);
      polygon.bindTooltip('这是个多边形');
      polygon.on('click', function(e) {
        alert('我是polygon');
      })
    }
  },
  mounted() {
    this.initLeaflet()
  }
}
</script>

<style>
#map {
  width: 100%;
  height: 100%;
}
.my-div-icon {
  font-size: 15px;
  width: 5px;
  color: red;
}
</style>

效果如图:

到这咱的简单的前端静态就差不多了

第三步: 前端接入后端接口

1. 修改端口

1. 后端

\src\main\resources\application.yml

修改port即可,把端口号由之前的8080,修改为了8081。(别一下子复制上去,到时候用户密码不一样啥的。)

server:
  port: 8081

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://127.0.0.1:5432/postgres
    username: postgres
    password: "0164523"
    druid:
      initial-size: 8
      min-idle: 5
      max-active: 10
      query-timeout: 6000
      transaction-query-timeout: 6000
      remove-abandoned-timeout: 1800
      filters: stat,config

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.history.gismap.model

logging:
  config: classpath:logback-boot.xml

2. 前端

vue-gismap/vite.config.js

Vite 项目没有 config/index.js 文件。端口号需要在项目根目录的 vite.config.js 中设置。 你需要打开 vite.config.js,在 server 选项里添加 port 字段。

修改port即可,把8080改成了8082。

export default defineConfig({
  // ... 其他配置
  server: {
    port: 8082,       // ▶ 修改前端端口为 8082
    open: true,       // 可选,自动打开浏览器
  },
  // ...
})

2. 解决跨域问题

因为前后端端口号不一样,所以会遇到跨域问题。

会提示:No 'Access-Control-Allow-Origin' header is present on the requested resource

就是因为跨域,前端无法请求后端数据。

解决跨域问题也很简单。后端加一个注释就可以,@CrossOrigin

\src\main\java\com\history\gismap\controller\MapController.java3. 

3. vue+axios网络请求

1.安装axios模块

npm install axios

2.axios get请求

一个非常简单的get请求,写在组件SpecialMap.vue里。

初始化加载getdata()函数,更新axiosdata数据,写在div中,vue用两个花括号{{}},在模板中引入数据。

<template>
  <div>
    {{axiosdata}}
    专题地图
  </div>
</template>

<script>
import axios from 'axios'
export default {
  data() {
    return {
      axiosdata: null
    }
  },
  methods: {
    getdata() {
      axios.get('http://localhost:8081/history/geometry?category=prefpgn&start=-5000&end=-221').then((response) => {
        this.axiosdata = response.data
        console.log(response.data)
      }).catch((response) => {
        console.log(response)
      })
    }
  },
  mounted() {
    this.getdata()
  }
}
</script>

唯一需要注意:请求的 URL 是 http://localhost:8081,如果后端端口变了,记得同步修改。另外,由于跨域已经在后端通过 @CrossOrigin 解决,前端无需额外配置。

模板搭建好了,leaflets能加载底图和覆盖物了,axios能和后端交互了,接下来要做的,就是把要实现的功能都实现了。

OK,今天就到这了,主要就是搭建前端静态页面,和改一下接口,下篇开始实现前后端结合的数据交换,大家心急的可以先看大佬的教程,我们下篇见,拜拜!

该项目并非原创(侵权了私聊删QAQ),而是翻新一个知乎上的大佬的spring boot 2 + vue 2 的项目, 项目教程:知乎的才华横溢吴道简 大佬的教程 
 

更多推荐