ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

WebGIS地震灾害可视化系统:从地图标注到数据分析的完整实战

WebGIS地震灾害可视化系统:从地图标注到数据分析的完整实战 简介这套WebGIS地震灾害可视化系统是面向高校毕业设计/课程设计的完整源码项目采用Python与WebGIS技术实现地震灾害数据的可视化展示功能涵盖地图标注与数据分析两大模块适合计算机及相关专业学生作为毕设参考或基础原型也便于科研人员快速验证思路。压缩包共788个文件约166.75MB主要包含Python源码.py、地理信息数据文件.shp/.dbf/.prj等、配置与说明文档.txt/.md/.bat、运行截图.png等代码结构与目录清晰便于定位核心逻辑。项目附带设计文档、数据库及基础地理数据如断层与台网数据经过严格测试可稳定运行可直接复现或二次开发配套说明文档和启动脚本也降低了环境配置门槛。目前已有105人学习适合需要快速搭建WebGIS可视化框架或完成地震灾害相关课题的读者。1. 为什么我把 WebGIS 地震灾害可视化做成毕业设计地图与数据分析不是两座孤岛当我把WebGIS地震灾害可视化这个选题报给导师时他第一句话就是你能不能在一个页面里既看到地震点的空间分布又看到每个区域的灾害损失统计这个问题恰好切中了多数毕业设计的痛点——要么地图做得很炫但数据是写死的要么分析图表单独一个页面、和地图完全割裂。这份资源是一套完整的 WebGIS 地震灾害可视化系统含 Python 后端、地图前端、数据库脚本与全套源码把地图标注、空间展示和数据分析串成了一条线。适合正在选毕业设计题目或课程设计要交系统的同学也适合想把 WebGIS 技术栈一次跑通、看懂边界在哪的从业者。它能帮你直接交付一套可以演示、可以答辩、可以改造成其他灾害可视化场景的完整系统。2. 系统架构拆解数据表、后端接口与前端图层的三层闭环拿到源码第一件事先别急着双击运行。我的习惯是把整个项目的目录结构、数据流向盘一遍否则后面改需求时会在模块之间迷路。这套系统的核心逻辑是数据库 → 后端 API → 前端图层三层数据链路每一层都对应着明确的文件。2.1 技术选型Python Flask Leaflet MySQL 的组合逻辑我在打开这套源码时最先注意到的是后端用了 Flask 而不是 Django。做过 Web 项目的同学都知道Django 自带 Admin 后台和 ORM功能全但偏重Flask 则是微框架适合做纯 API 服务。这套系统里前端地图用的是 Leaflet 1.7.1图表部分接了 ECharts数据存在 MySQL 中。这个选型在毕业设计场景里是合理的——Leaflet 轻量、插件生态足够支撑标注和聚合ECharts 做统计图表不需要自己啃 CanvasFlask 写 JSON 接口比 Django 省不少样板代码。提示如果你在别的电脑上复现注意 Python 版本最好在 3.7 到 3.9 之间。这套源码里用的依赖库尤其是 Flask-SQLAlchemy 和 PyMySQL 的版本组合在 Python 3.10 下偶尔会出现兼容告警虽然多半能跑但答辩现场出现告警总归不好看。项目目录里应该有类似于这样的结构webgis_earthquake/ ├── app.py # Flask 主入口注册路由 ├── config.py # 数据库连接配置 ├── models.py # SQLAlchemy 数据模型 ├── static/ │ ├── css/ │ ├── js/ │ │ ├── map_main.js # Leaflet 地图初始化与标注逻辑 │ │ └── chart_main.js # ECharts 图表渲染 │ └── data/ # 本地 GeoJSON 或 CSV 测试数据 ├── templates/ │ └── index.html # 主页面 └── sql/ └── earthquake_db.sql # 数据库初始化脚本这段目录结构说明了两件事第一前后端逻辑分离得比较干净改地图交互不会动到后端接口第二sql 目录下的脚本是整套系统的地基数据库跑不起来后面所有功能都是空的。2.2 数据库设计震点表、灾害损失表与标注点表我打开 sql 初始化脚本后发现表结构设计是这套系统里信息量最大的部分。它不止有一张地震目录表而是拆了三张核心表地震震点表、区域灾害损失表、地图标注点表。这样的拆分逻辑是震点表管地震发生在哪灾害表管造成多大影响标注表管用户在地图上手动标记了什么。三类数据混在一张表里会非常难维护。CREATE TABLE earthquake_point ( id INT PRIMARY KEY AUTO_INCREMENT, longitude DECIMAL(10, 6) NOT NULL, latitude DECIMAL(10, 6) NOT NULL, magnitude DECIMAL(3, 1) NOT NULL, depth DECIMAL(5, 1) DEFAULT 0, location_name VARCHAR(100), occurrence_time DATETIME NOT NULL, INDEX idx_lon_lat (longitude, latitude), INDEX idx_time (occurrence_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张震点表里longitude和latitude用的都是DECIMAL(10,6)能精确到小数点后 6 位对应的地面精度约 0.1 米对地震展示来说完全够用。magnitude用DECIMAL(3,1)是因为震级最多到 10 级一位小数足够表达。表里建了两个索引一个在经纬度上用于地图范围查询一个在时间上用于按时间段过滤——这是后续地图拖拽查询和年份筛选是否流畅的关键。CREATE TABLE disaster_loss ( id INT PRIMARY KEY AUTO_INCREMENT, region_name VARCHAR(100) NOT NULL, earthquake_id INT, affected_population INT DEFAULT 0, collapsed_buildings INT DEFAULT 0, economic_loss DECIMAL(12, 2) DEFAULT 0, disaster_level VARCHAR(10), FOREIGN KEY (earthquake_id) REFERENCES earthquake_point(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;灾害损失表通过earthquake_id外键关联到震点表这样一次地震就对应一个区域的损失记录。disaster_level字段存储的是特别重大、重大、较大、一般这类等级描述这个字段在后续数据分析模块会用作分组统计的依据。2.3 后端 API 设计按区域、按时间过滤的数据接口我一般会先看后端的路由设计判断一个项目的数据接口是不是够用了。这套系统里 Flask 侧提供了几个关键接口获取全部震点、按时间范围过滤震点、按区域统计灾害损失、获取标注列表。其中按时间过滤这个接口在毕业设计答辩时是最常被演示的因为评审老师通常不会自己画图而是直接问你能不能只看某一年之后的地震。from flask import Flask, jsonify, request from flask_sqlalchemy import SQLAlchemy from datetime import datetime app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://root:passwordlocalhost/earthquake_db?charsetutf8mb4 db SQLAlchemy(app) class EarthquakePoint(db.Model): __tablename__ earthquake_point id db.Column(db.Integer, primary_keyTrue) longitude db.Column(db.Numeric(10, 6)) latitude db.Column(db.Numeric(10, 6)) magnitude db.Column(db.Numeric(3, 1)) occurrence_time db.Column(db.DateTime) app.route(/api/earthquakes, methods[GET]) def get_earthquakes(): start request.args.get(start, 1900-01-01) end request.args.get(end, 2100-01-01) min_mag request.args.get(min_magnitude, typefloat, default0) query EarthquakePoint.query.filter( EarthquakePoint.occurrence_time datetime.strptime(start, %Y-%m-%d), EarthquakePoint.occurrence_time datetime.strptime(end, %Y-%m-%d), EarthquakePoint.magnitude min_mag ) results query.all() return jsonify([{ id: e.id, lng: float(e.longitude), lat: float(e.latitude), mag: float(e.magnitude), time: e.occurrence_time.strftime(%Y-%m-%d) } for e in results])这段代码的逻辑不复杂但有几个参数值得较真。min_magnitude这个过滤条件很容易被忽略实际演示时如果地图上把所有小震级地震都画出来点位会叠成一团根本看不出空间分布规律。接口返回字段里特意把lng和lat分开命名而不是用longitude、latitude的长名是为了让前端拿到 JSON 后能直接丢给 Leaflet 的L.marker([lat, lng])少一次字段映射就少一个出错的地方。3. 地图标注实战从坐标转换到 Popup 交互地图模块是这套系统最抓眼球的部分。如果你把 Leaflet 的官方示例跑通过一次再来看这套系统的地图代码会发现它不只是简单地L.marker().addTo(map)而是在标注之上叠加了分级渲染、Popup 联动和图层聚合。这些细节决定了地图是能看还是能说服人。3.1 坐标系统与投影转换WGS84、GCJ-02 与 Web Mercator新手最容易犯的错误是拿着高德地图的坐标去喂 Leaflet结果点位凭空偏移了几百米甚至几公里。这里面的原因不是 Leaflet 算错了而是坐标基准没对齐。Leaflet 默认使用 WGS84 经纬度配合 Web Mercator 投影在底图上渲染高德地图用的是 GCJ-02火星坐标百度更是有自己的 BD-09。如果数据采集来源是国内的公开地震目录用的多半是 WGS84但如果你从某些第三方平台爬到的数据混了 GCJ-02就必须做转换。import math def gcj02_to_wgs84(lng, lat): a 6378245.0 ee 0.00669342162296594323 dlat _transform_lat(lng - 105.0, lat - 35.0) dlng _transform_lng(lng - 105.0, lat - 35.0) radlat lat / 180.0 * math.pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlng (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) return lng dlng, lat dlat这段转换代码是标准的 GCJ-02 逆转换直接用了算法公式没有依赖第三方库避免引入coord_transform之类的包增加部署负担。注意代码里_transform_lat和_transform_lng还需要补全通常是一组多项式偏移实际源码里会有完整实现。我要说的是一个更实用的建议如果源数据里混了 GCJ-02在入库前就统一转成 WGS84不要在每次请求接口时边取边转。数据库里存两套坐标前端请求时要判断数据来源这个逻辑越到后期越混乱。3.2 震点标注Marker、CircleMarker 与自定义 IconLeaflet 里标注地震点有多种方式最朴素的是L.marker但纯 Marker 的图标是一个水滴形状跟地震场景不搭。这套系统里主要用的是L.circleMarker因为它可以直接按震级设置半径和颜色视觉上比图标标注更直观而且大量渲染时比普通 Marker 的性能好得多。function renderEarthquakeLayer(data, map) { var geojson { type: FeatureCollection, features: data.map(function(d) { return { type: Feature, geometry: { type: Point, coordinates: [d.lng, d.lat] }, properties: { mag: d.mag, time: d.time, location: d.location_name || 未知区域, id: d.id } }; }) }; var layer L.geoJSON(geojson, { pointToLayer: function(feature, latlng) { var mag feature.properties.mag; return L.circleMarker(latlng, { radius: mag * 2.5, color: getColorByMagnitude(mag), fillOpacity: 0.6 mag * 0.04 }); }, onEachFeature: function(feature, layer) { layer.bindPopup( b feature.properties.location /bbr 震级: feature.properties.mag br 时间: feature.properties.time ); } }).addTo(map); }radius: mag * 2.5这个参数是经验值震级 5 对应半径 12.5 像素在普通分辨率屏幕上看大小适中。如果你把系数改成 45 级地震的标注就会大到遮挡相邻点位。getColorByMagnitude是一个按震级区间返回颜色的函数常见做法是 3 级以下绿色、4-5 级黄色、6 级及以上红色。Popup 里展示位置、震级、时间三个字段正好对应答辩时老师最可能问的三个信息。3.3 图层分组与聚合Cluster 插件解决点密集问题地震目录数据一旦放到全国范围点位数量通常有几千甚至上万条。全部直接渲染会让浏览器卡顿到无法交互这时候最常规的解法是引入 Leaflet.markercluster 插件做聚合。聚合的思路是把距离相近的点合并成一个带数字的圆形图标缩放级别变化时实时拆散或合并。var clusterLayer L.markerClusterGroup({ maxClusterRadius: 60, iconCreateFunction: function(cluster) { var count cluster.getChildCount(); return L.divIcon({ html: div classcluster-num count /div, className: cluster-icon, iconSize: L.point(40, 40) }); } }); data.forEach(function(d) { var marker L.marker([d.lat, d.lng], { title: d.location_name }); marker.bindPopup(震级: d.mag br时间: d.time); clusterLayer.addLayer(marker); }); map.addLayer(clusterLayer);maxClusterRadius: 60表示两个点的屏幕距离小于 60 像素时才会被聚合。这个值不是越大越好如果你设成 200放大到省级视图时点位仍然会黏在一起反而失真。我自己在调试时会把聚合半径从 40 开始往上加每加一次就缩小地图看一眼聚合效果。另外注意iconCreateFunction里用了L.divIcon而不是图片因为聚合数量是动态变化的用图片没法实时更新数字。4. 数据分析可视化震级分布、时间趋势与区域对比光有地图标注系统只能算可视化还不能叫分析。这套系统的数据分析模块覆盖了三个典型的统计视角震级分布看结构、时间序列看趋势、区域对比看差异。这三个方向也是地震灾害相关论文里最常用的三类图。4.1 震级分级统计用 Pandas 分组聚合数据统计部分在后端用 Pandas 算好前端只负责渲染图表。之所以不在前端算是因为浏览器对几千条数据的遍历虽然能跑但算分组和聚合时代码写着费劲后端一次性把结果算好返回给前端前端拿到的就是一个可以直接画图的数组。import pandas as pd def magnitude_distribution(): df pd.read_sql(SELECT magnitude, COUNT(*) AS cnt FROM earthquake_point GROUP BY magnitude, db.engine) bins [0, 3, 4, 5, 6, 10] labels [3级以下, 3-4级, 4-5级, 5-6级, 6级以上] df[level] pd.cut(df[magnitude], binsbins, labelslabels, rightFalse) result df.groupby(level, observedFalse)[cnt].sum().reset_index() return result.to_dict(orientrecords)pd.cut是分箱统计的核心函数bins定义了震级区间边界rightFalse表示左闭右开即 3 级以下包含 0 到 2.9 级3-4 级包含 3.0 到 3.9 级。这里有一个容易翻车的地方如果原数据里存在震级为 0 或负数的异常记录某些旧目录里会用 0 表示无震级pd.cut会直接报错或把这些记录归到 NaN 组。我一般会先执行df df[df[magnitude] 0]做一次过滤再分箱。4.2 年度趋势折线ECharts 与前端的对接方式年度趋势图是答辩时最容易被追问的图因为它直接反映了地震活跃度随时间的变化。后端按年份聚合出每年地震次数前端用 ECharts 折线图渲染。后端返回的数据结构是[{year: 2018, count: 32}, ...]前端拿到后直接塞进 ECharts 的xAxis.data和series.data。fetch(/api/earthquake/yearly_trend) .then(response response.json()) .then(data { var chart echarts.init(document.getElementById(trendChart)); chart.setOption({ xAxis: { type: category, data: data.map(d d.year) }, yAxis: { type: value, name: 地震次数 }, series: [{ type: line, data: data.map(d d.count), smooth: true, areaStyle: { opacity: 0.15 }, lineStyle: { width: 2 } }], tooltip: { trigger: axis } }); });smooth: true会让折线变成平滑曲线视觉上更学术但如果你在做的是严格的统计报告建议把它关掉——平滑曲线会掩盖真实数据的波动细节。areaStyle.opacity设置的是折线下方填充色的透明度数值太高会遮挡网格线0.15 是我个人偏好的值。trigger: axis是 ECharts 的 tooltip 联动模式鼠标在图上滑动时坐标轴十字线上所有系列的数据都会显示在年度趋势图这种单系列图里用trigger: item也行但如果你后续加了多条线一定记得改回来。4.3 区域灾害对比柱状图与地图联动区域对比是这套系统里最能体现WebGIS价值的功能。它的核心不是画一张柱状图而是点地图上的某个区域柱状图跟着变点柱状图里的某根柱子地图自动定位到对应区域。这种联动交互在答辩现场的效果非常加分因为老师能直观看到数据筛选与空间定位的双向反馈。function regionBarChart(regionData) { var chart echarts.init(document.getElementById(barChart)); chart.setOption({ xAxis: { type: category, data: regionData.map(r r.region_name) }, yAxis: { type: value, name: 经济损失万元 }, series: [{ type: bar, data: regionData.map(r r.economic_loss), itemStyle: { color: function(param) { var val param.value; return val 5000 ? #c0392b : (val 1000 ? #e67e22 : #27ae60); } } }] }); chart.on(click, function(params) { var regionName params.name; map.flyToBounds(findRegionBounds(regionName), { duration: 1.5 }); }); }itemStyle.color用了一个函数而不是固定颜色这样每个柱子在渲染时根据数值大小动态取色。map.flyToBounds是 Leaflet 的飞行动画接口findRegionBounds需要预先维护一个区域名到经纬度范围的映射表。这里有个细节如果你直接用map.setView([lat, lng], zoom)去跳转只能定位到区域中心点用flyToBounds能把整个区域边界自适应地放进视口演示效果更专业。5. 避坑排查部署与运行中我踩过的六个坑这套系统我在不同机器上部署过三次每次都遇到不同的问题。下面这几条是从运行日志和浏览器控制台里整理出来的高频坑按出现概率排序希望你能在我踩过的地方直接绕过去。5.1 地图白屏JS 文件没加载出来现象页面打开了但地图区域是灰白色浏览器控制台报错Leaflet is not defined或者map is undefined。原因常见有两种。第一种是直接用了官方 CDN 的 Leaflet 文件但部署环境是内网或校园网CDN 被墙或加载超时第二种是本地引用的 js 路径写错了比如static/js/leaflet.js实际文件名是leaflet.min.js。解决把 Leaflet 和 ECharts 的 js、css 文件全部下载到本地 static 目录用相对路径引用。一套系统的前端资源应该和项目同生命周期不要赌外部 CDN 的可用性。确认路径时在浏览器控制台执行document.querySelectorAll(script[src])逐个检查资源的 HTTP 加载状态。5.2 坐标偏移WGS84 与 GCJ-02 混用现象图上的震点位置和底图地理位置对不上整体偏移约 300-500 米。原因地震目录数据来自多个渠道一部分是国标 WGS84一部分是被别人转换过坐标后的 GCJ-02。我遇到过一次源数据里两种坐标混在一起的情况。解决统一在数据入库时做一次检测把同一个已知经纬度的点位在地图上渲染出来如果它和真实位置的偏差超过 100 米说明该批数据需要做坐标逆转换。比较好的预防方式是写一个自动化脚本遍历所有数据对疑似 GCJ-02 的点调用转换函数。判断依据是如果所有点位都偏移且偏移方向一致几乎可以肯定是坐标基准问题。5.3 数据库连接失败端口与字符集现象后端启动时报错Cant connect to MySQL server on localhost。原因MySQL 服务没有启动或者连接 URL 里的端口被改了。毕业设计用的本地环境MySQL 默认端口 3306但有些机器装了多个版本或者被安全软件占用了端口。另外连接 URL 里如果漏了?charsetutf8mb4前端拿到的中文会乱码。解决启动前先执行mysql -u root -p确认 MySQL 能起来再用SHOW VARIABLES LIKE port;确认实际端口。连接 URL 写成mysqlpymysql://root:yourpasswordlocalhost:3306/earthquake_db?charsetutf8mb4同时确保数据库名和配置里的earthquake_db一致——大小写和拼写错一个字母都是白屏。5.4 前端请求跨域Flask-CORS 配置现象浏览器控制台报Access to XMLHttpRequest at http://127.0.0.1:5000/api/... from origin http://localhost:5500 has been blocked by CORS policy。原因前端用了 Live Server 或者直接从文件系统打开 HTML页面运行在 5500 端口而 Flask 跑在 5000 端口两者不同源。Flask 默认不允许跨域。解决安装 Flask-CORS 并在 app 初始化时注册from flask_cors import CORS CORS(app, resources{r/api/*: {origins: *}})origins: *表示所有来源都放行做毕业设计演示没问题。如果你部署到生产环境建议把 origins 换成具体的域名否则别人可以跨域调用你的接口。另外一个小建议开发时直接用 Flask 的默认 5000 端口访问前端页面也就是把 index.html 放到templates目录里用render_template返回这样天然同源省去 CORS 的麻烦。5.5 中文乱码数据入库前的编码统一现象前端页面上区域名称显示成???或æµ·å¾·。原因MySQL 表字符集建表时用了latin1或者连接串里没指定charsetutf8mb4也可能是数据文件本身是 GBK 编码。解决建表时统一指定DEFAULT CHARSETutf8mb4导入 CSV 或 Excel 数据时用 Pandas 的encodingutf-8参数读取。注意一个细节如果原数据是 GBK 编码直接指定utf-8会抛UnicodeDecodeError这时要先读取为 GBK 再转成 UTF-8。一个更简单的排查方法是用 Navicat 或命令行查看表数据如果显示正常、页面乱了问题在前端传输如果数据库里就已经是乱码问题在导入环节。5.6 大点集卡顿合理设置聚合参数现象地图缩放级别小缩小时操作流畅放大到省级或市级视图后拖动地图掉帧明显。原因放大后聚合被打散几千个circleMarker全部渲染在画布上超出了浏览器的 2D Canvas 性能上限。OpenLayers 和 Leaflet 在渲染几千个点时的差异就在这里Leaflet 是 SVG 或 Canvas 二选一点多了都会吃力。解决除了前面说的聚合插件外还要注意renderer的选择。初始化地图时用L.tileLayer配合renderer: L.canvas()让所有矢量图层统一走 Canvas 渲染而不是默认的 SVG性能会明显提升。同时把聚合半径从 60 调到 40让更多点在低缩放级别时保持聚合状态。这两个参数组合改完同样几千条数据在普通笔记本上拖动就不会明显卡顿了。6. 进阶用法把系统从「能答辩」升级成「能演示」一套系统能跑和能演示之间差的不是功能多而是交互节奏。接下来这三个进阶玩法是我在源码基础之上自己加的不需要大改架构但演示效果会提升一个档次。6.1 时间轴回放用滑块控制地震点的逐年回放这个功能的核心是一个input[typerange]滑块绑定在 Leaflet 地图旁边。每次滑块值变化时前端重新向后端请求对应年份的数据然后清空地图上的震点图层并重新渲染。关键点在于清空图层时要用layer.clearLayers()而不是直接map.removeLayer(layer)再新建否则会创建大量残留图层对象内存泄漏肉眼可见。var yearSlider document.getElementById(yearSlider); var eqLayer L.geoJSON().addTo(map); yearSlider.addEventListener(input, function() { var year this.value; fetch(/api/earthquakes?start year -01-01end year -12-31) .then(res res.json()) .then(data { eqLayer.clearLayers(); renderEarthquakeLayer(data, map); document.getElementById(yearLabel).textContent year 年; }); });时间轴回放还有一个隐藏好处它天然支持分段对比。演示时先拖到 2008 年再拖到 2015 年老师一眼就能看出不同时期的地震活跃区域差异比静态页面有说服力得多。6.2 一键导出分析报告PDF 与图表打包答辩前通常会需要一份纸质或 PDF 版的分析报告。手动截图贴图表不仅耗时还容易漏数据。我写了一个小脚本后端用flask类库把数据库里的统计结果导出成 JSON前端用echarts的getDataURL()方法把图表转成 base64 图片再加一个简单的html2pdf库把页面打包成 PDF。这个技巧用到的不是复杂技术但能让你在答辩材料准备上省下整整半天。6.3 答辩演示的动线安排与常见提问最后分享一个不太算技术、但直接影响成绩的动线我先打开地图总览展示全国范围内的震点聚合与聚合结果再放大到某一区域点击具体震点展示 Popup 中的震级和时间然后切换到统计图表把震级分布和年度趋势交叉对比最后回归到地图上解释空间聚集与灾害损失的关系。整个流程严格控制在 10 到 12 分钟。注意答辩老师最常问的三个问题基本绕不开一是坐标用什么投影、为什么不用度分秒二是聚合的阈值怎么定的三是数据量如果扩大十万条系统还能不能扛住。前两个问题看代码里的maxClusterRadius和DECIMAL(10,6)就能回答第三个问题要提前准备说法常见答案是前端走聚合降采样 后端按时间过滤只返回必要数据不要硬着头皮说没问题实话实说并补充优化思路反而更稳妥。这套系统最难得的地方不是某一项技术多深而是它把地图和数据分析放在同一个页面里互相联动这恰好是 WebGIS 这个方向最核心的空间 属性一体化的实践。从那以后我每次拿到新的可视化项目都会强制自己先跑一遍坐标转换和数据入库的校验再开始写页面逻辑——这一步帮我避开了大部分白屏和偏移的坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表