欢迎来到 WebGIS 入门系列的第十四篇文章!在本文中,我们将介绍如何利用 Node.js 来实现 WebGIS 系统开发时后台接口的开发知识,其中包括数据库初始化、数据表创建、后端接口程序初始化、Vue 项目中路由配置等。
调整 Vue 项目路由配置
调整 src 目录下 App.vue 组件中的代码,配置菜单跳转地址及 router-view 信息:
<template>
<header>
<h1>我的 WebGIS 应用</h1>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/map">地图</a></li>
<li><a href="/data">数据管理</a></li>
</ul>
</nav>
</header>
<main>
<router-view></router-view>
</main>
</template>
<script setup lang="ts"></script>
<style>
// ......
</style>
在 src/views 目录下分别新建 MapView.vue 和 DataManageView.vue 组件:
// MapView.vue
<template>
<main class="map-main">
<MapView />
</main>
</template>
<script setup lang="ts">
import MapView from '@/components/MapView.vue'
</script>
<style>
.map-main {
width: 100%;
height: 100%;
}
</style>
// DataManageView.vue
<template>
<main class="data-main">
<DataManage />
</main>
</template>
<script setup lang="ts">
import DataManage from '@/components/DataManage.vue'
</script>
<style>
.data-main {
width: 100%;
height: 100%;
}
</style>
在 src/components 目录下新建 DataManage.vue 组件:
<template>
<div class="data-manage">data manage</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.data-manage {
width: 100%;
height: 100%;
}
</style>
优化调整 src/router 目录下的路由配置信息:
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/map',
name: 'map',
component: () => import('../views/MapView.vue')
},
{
path: '/data',
name: 'data',
component: () => import('../views/DataManageView.vue')
}
]
})
export default router
界面预览如下所示:
优化 DataManage 组件
在 src/components 目录下的 DataManage.vue 组件中拷贝下面代码,完善数据管理界面:
<template>
<div class="data-manage">
<div class="data-manage-form">
<div class="data-manage-header">
<el-alert title="填入以下信息后进行数据入库" type="info" show-icon />
</div>
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="区域">
<el-select v-model="form.region" placeholder="请选择">
<el-option
v-for="item in regionData"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="日期">
<el-date-picker v-model="form.date" type="date" placeholder="请选择" style="width: 50%" />
</el-form-item>
<el-form-item label="已激活">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="激活类型">
<el-checkbox-group v-model="form.type">
<el-checkbox
v-for="item in typeData"
:key="item.value"
:label="item.value"
:name="item.value"
>{{ item.label }}</el-checkbox
>
</el-checkbox-group>
</el-form-item>
<el-form-item label="资源">
<el-radio-group v-model="form.resource">
<el-radio v-for="item in resourceData" :key="item.value" :label="item.value">{{
item.label
}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
<div class="data-manage-table">
<div class="data-manage-header">
<el-alert title="入库数据反显" type="info" show-icon />
</div>
<el-table :data="tableData" height="400" style="width: 100%" empty-text="暂无数据">
<el-table-column
v-for="item in tableColumn"
:key="item.prop"
:prop="item.prop"
:label="item.label"
/>
</el-table>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
const regionData = ref([
{
value: 'beijing',
label: '北京'
},
{
value: 'shanghai',
label: '上海'
},
{
value: 'chengdu',
label: '成都'
}
])
const typeData = ref([
{
value: 'type-1',
label: '类型一'
},
{
value: 'type-2',
label: '类型二'
},
{
value: 'type-3',
label: '类型三'
},
{
value: 'type-4',
label: '类型四'
}
])
const resourceData = ref([
{
value: 'resource-1',
label: '资源一'
},
{
value: 'resource-2',
label: '资源二'
}
])
const tableColumn = ref([
{
prop: 'name',
label: '名称'
},
{
prop: 'region',
label: '区域'
},
{
prop: 'date',
label: '日期'
},
{
prop: 'delivery',
label: '已激活'
},
{
prop: 'type',
label: '激活类型'
},
{
prop: 'resource',
label: '资源'
},
{
prop: 'desc',
label: '备注'
}
])
const tableData = ref([])
const form = reactive({
name: '',
region: '',
date: '',
delivery: false,
type: [],
resource: '',
desc: ''
})
const onSubmit = () => {
console.log('submit!', JSON.parse(JSON.stringify(form)))
}
</script>
<style scoped>
.data-manage {
width: 100%;
height: 100%;
display: flex;
padding: 16px;
box-sizing: border-box;
}
.data-manage-header {
margin-bottom: 16px;
}
.data-manage-form {
width: 480px;
border: 1px solid #dcdfe6;
border-radius: 4px;
margin-right: 8px;
padding: 16px;
box-sizing: border-box;
}
.data-manage-table {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 16px;
box-sizing: border-box;
}
</style>
界面预览如下:
上述界面操作逻辑:左侧数据入库面板中依次填入相关信息后点击下方“提交”按钮,用户输入的信息数据会被提交到后台数据库中,此时右侧数据预览面板会更新,显示最新数据库中的数据。
数据库初始化
数据在前端界面被用户输入后,会经过后端接口程序,最终保存在数据库中,数据库可以安装部署在本地开发环境或远端服务器环境中。
此系列文章中为了简化操作,在腾讯云租了一个 PostgreSQL 云数据库,所以省去了安装部署流程,如果想安装部署在本机或服务器的话,建议大家通过搜索引擎去寻找一些相关教程,无脑安装即可。
数据库安装部署之后,接下来进行库表初始化。
首先在 PostgreSQL 数据库管理面板中选择新建数据库,名称为“webgis”:
然后选择新建的“webgis”数据库进入该数据库,在 public 模式下选择操作中的“新建表”,新建一份数据表,其结构如下:
至此,一个名为“webgis”的数据库创建成功,里面又新建了一份名为“webgis_project_bilibili”的数据表,我们后续入库的数据就存放在此。
后端接口程序初始化
为了降低后端接口开发门槛,避免短时间内又学习一门新的开发语言,此系列的后端接口开发我们使用 Node.js,它是一个类似于浏览器的 JavaScript 运行环境,允许 JavaScript 语言编写的程序在浏览器环境之外可以运行,简单理解就是:Node.js 让 JavaScript 可以在浏览器之外运行成为了可能!
Node.js 在使用之前必须要在本机安装部署,不过不用担心,我们已经安装部署过了,详见《6、快速上手:使用 Vue 3 创建您的第一个 WebGIS 地图》。
在现有的项目根目录下新建 node 目录,然后通过 npm init 命令初始化一个后端接口程序目录,此时该目录下会出现一个 package.json 文件,里面包含当前后端程序的项目名称、版本号、作者信息等,以及后续我们安装的依赖包信息。
运行命令 npm install express 安装 express 框架。
新建 index.js 文件,将下述代码拷贝至此:
// eslint-disable-next-line no-undef
var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('hello world')
})
app.listen(3001)
通过上述操作,我们已经初始化了一个基础的后端接口程序,其中已经新建了一个路径为“/”的 get 类型接口,通过 node index 命令将其启动后,在浏览器地址栏中输入“:3001/”,可以访问该接口:
GET 和 POST 接口开发
GET 和 POST 接口逻辑中都涉及到从 PostgreSQL 数据库中获取数据和插入数据,所以后端接口程序中需要安装 pg 模块,通过命令 npm install pg 安装。
在 node 目录下新建 routers 目录,然后该目录下新建 user.js 文件,在此文件中分别完成用户数据获取和插入接口:
// eslint-disable-next-line no-undef
var express = require('express')
var pg = require('pg')
var router = express.Router()
// postgres://[数据库用户名]:[数据库密码]@[数据库地址及端口]/[数据库名称]
var pgConfig = "postgres://postgres:webgis@localhost:5432/webgis";
// 获取全部用户数据
router.get('/all-data', function (req, res) {
var client = new pg.Client(pgConfig)
client.connect(function (isErr) {
if (isErr) {
console.log('connect error:' + isErr.message)
client.end()
return
}
client.query('SELECT * FROM "webgis_project_bilibili"', function (isErr, rst) {
if (isErr) {
console.log('query error:' + isErr.message)
res.send({
status: 'fail',
msg: 'query error'
})
} else {
console.log('query success, data is: ' + rst)
res.send({
status: 'success',
data: rst.rows
})
}
client.end()
})
})
})
// 插入数据
router.post('/insert-data', function (req, res) {
var id = Number(req.body.id)
var name = req.body.name
var region = req.body.region
var date = req.body.date
var delivery = Boolean(req.body.delivery)
var type = req.body.type
var resource = req.body.resource
var desc = req.body.desc
var client = new pg.Client(pgConfig)
client.connect(function (isErr) {
if (isErr) {
console.log('connect error:' + isErr.message)
client.end()
return
}
client.query(
'INSERT INTO "webgis_project_bilibili" ("id", "name", "region", "date", "delivery", "type", "resource", "desc") VALUES ($1, $2, $3, $4, $5, $6, $7, $8);',
[id, name, region, date, delivery, type, resource, desc],
function (isErr, rst) {
if (isErr) {
console.log('query error:' + isErr.message)
res.send({
status: 'fail',
msg: 'insert error'
})
} else {
console.log('insert success, data is: ' + rst)
res.send({
status: 'success',
data: []
})
}
client.end()
}
)
})
})
// eslint-disable-next-line no-undef
module.exports = router
优化 node 目录下的 index.js 文件,在其中配置接口跨域、body数据解析、接口地址等信息:
/* eslint-disable no-undef */
// eslint-disable-next-line no-undef
var express = require('express')
var app = express()
var bodyParser = require('body-parser')
var user = require('./routers/user')
//设置跨域访问
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header(
'Access-Control-Allow-Headers',
'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With'
)
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
res.header('X-Powered-By', ' 3.2.1')
res.header('Content-Type', 'application/json;charset=utf-8')
next()
})
app.use(
bodyParser.urlencoded({
extended: true
})
)
app.use(bodyParser.json())
app.use('/user', user)
app.get('/', function (req, res) {
res.send('hello world')
})
app.listen(3001)
至此,接口开发工作已完成。
DataManage 中数据插入和获取
在 DataManage.vue 组件中,优化完善 onSubmit 方法:
const onSubmit = () => {
const formData = JSON.parse(JSON.stringify(form))
axios
.post(
'http://localhost:3001/user/insert-data',
qs.stringify({
...formData,
type: formData.type.join(','),
id: new Date().getTime()
})
)
.then((res) => {
openMessage('数据入库成功', 'success')
fetchData()
})
.catch((err) => {
console.error('err', err)
openMessage('数据入库失败', 'error')
})
}
每次入库成功后,都需要重新获取一遍数据库中的最新数据,所以需要定义获取数据的方法 fetchData:
function fetchData() {
axios
.get('http://localhost:3001/user/all-data')
.then((res) => {
console.log('res', res)
tableData.value = lodash.map(res?.data?.data, (item) => {
return {
...item,
region: lodash.find(regionData.value, { value: item.region })?.label,
date: new Date(item.date).toLocaleDateString(),
delivery: item.delivery ? '是' : '否',
type: item.type.split(',').map((type) => {
return lodash.find(typeData.value, { value: type })?.label
}),
resource: lodash.find(resourceData.value, { value: item.resource })?.label
}
})
})
.catch((err) => {
console.error('err', err)
})
}
onMounted(() => {
fetchData()
})
界面预览如下:
小作业:数据管理支持检索数据
对于 WebGIS 系统中前端和后端开发流程全部介绍完毕,数据管理界面完成了数据插入和获取,但是在数据获取时并没有按条件去筛选,接下来继续优化 GET 接口,使其按接口传参来获取相应的数据,并在界面中增加条件筛选模块。
结语
通过本文的介绍,应该对如何使用 Node.js 开发 WebGIS 系统的后台接口有了基本的了解。从数据库的初始化到后端接口的创建,再到 Vue 项目中的路由配置,每一步都是构建一个高效、可靠的 WebGIS 系统不可或缺的部分。完成小作业可以帮助巩固所学知识,并将其应用到实际的项目开发中。