逐福科技
177-371-24501(同微信)

微信小程序开发之OFO共享单车分析,小程序的地图组件

2022-04-21 管理员
想学一下微信小程序,发现文档这东西,干看真没啥意思。所以打算自己先动手撸一个。摩拜单车有自己的小程序,基本功能都有,方便又小巧,甚是喜爱。于是我就萌生了一个给ofo共享单车撸一个小程序(不知道为啥ofo没有小程序)的想法。
这一节我们先来剖析一下小程序的地图组件

1.准备工作

微信小程序当然属于腾讯大佬的(给大佬递茶):微信小程序开发者工具下载,腾讯开放了小程序个人开发平台,只需要一个微信号就可以成为小程序开发者了。

2.小程序页面

打开小程序开发者工具,用微信扫码登录,创建一个默认的小程序。

pages文件夹下存放着小程序所有的业务页面;
index文件夹就是一个页面,index.wxml是页面的结构文件,类似html。
index.wxss是页面的样式,其实就是css;index.js是页面的逻辑,数据请求与渲染都是都在这个页面完成。
logs文件夹存放着小程序开发日志,目前暂时用不到。
utils.js可以编写自己的JavaScript插件也可以引入第三方插件。
app.js处理全局的一些逻辑,比如定义全局变量存放获取的用户信息,这样每个页面都可以获取用户信息。
app.json 是全局配置文件,比如设置标题栏的背景色等。
app.wxss 存放页面的公共样式,如果多个页面需要用到同一样式,就可以写在这里。
调试按钮用来进行调试,功能和chrome dev调试工具差不多。
项目按钮显示预览二维码,用于真机调试。必须真机调试测试代码,自带的调试工具有时候会出bug

3.创建页面结构

上一节已经分析了默认的文件结构以及它们的功能,现在我们要创建ofo小程序所需要的页面。
删除pages下默认的index文件夹,logs/utils文件夹可选择性删除
在与pages同级目录下创建images文件夹,存放页面需要用到的图标,下载图标
本小程序不需要在app.js里面编写内容,可以先注释这里面的代码
在app.json里,删掉默认代码,编写如下代码(注:app.json文件里不能有任何注释,这里是为了描述页面功能更直观):
  1. {
  2.   "pages":[
  3.     "pages/index/index", // 地图页
  4.     "pages/warn/index",  // 车辆报障页
  5.     "pages/scanresult/index", // 扫码成功页
  6.     "pages/billing/index", // 开始计费页
  7.     "pages/my/index", // 账户页
  8.     "pages/wallet/index", // 钱包页
  9.     "pages/charge/index", // 充值页
  10.     "pages/logs/logs" // 日志页
  11.   ],
  12.   "window":{
  13.     "backgroundTextStyle":"light",
  14.     "navigationBarBackgroundColor": "#b9dd08", // 标题栏背景色
  15.     "navigationBarTitleText": "ofo 共享单车",  // 标题栏文字
  16.     "navigationBarTextStyle":"black" // 标题栏文字样式
  17.   }
  18. }
复制代码

在app.wxss里面。我们先把页面的通用样式给出来(这一步我是在所有页面写完后,发现有些页面有着相同的样式,再把公共样式抽离出来放在app.wxss里面的。)
  1. /**app.wxss**/
  2. .container{
  3.         background-color: #f2f2f2;
  4.         height: 100vh;
  5. }
  6. .title{
  7.         background-color: #f2f2f2;
  8.         padding: 30rpx 0 30rpx 50rpx;
  9.         font-size: 28rpx;
  10.         color: #000;
  11. }
  12. .tapbar{
  13.         display: flex;
  14.         align-items: center;
  15.         justify-content: space-between;
  16.         background-color: #fff;
  17.         padding: 40rpx;
  18. }
  19. .btn-charge{
  20.         width: 90%;
  21.         background-color: #b9dd08;
  22.         margin: 40rpx auto 30rpx;
  23.         text-align: center;
  24. }

保存后,你的pages文件夹下就是这样的界面了(在app.json下创建路径会自动创建文件夹,贼方便):


4.编写地图首页 (index文件夹)

先来看一下地图首页效果图


页面分析:
  • 整页显示地图,宽高占手机窗口的100%
  • 地图之上有五个按钮图标和多个黄色ofo标记:定位按钮,立即用车按钮,举报按钮,黄色头像按钮和位于地图中心的定位标记。


要在页面显示地图,我们需要在index.wxml引入地图组件:
  1. <!--index.wxml-->
  2. <view class="container">
  3.   <map id="ofoMap"
  4.     latitude="{{latitude}}"  // 纬度
  5.     longitude="{{longitude}}"  // 经度
  6.     scale="{{scale}}"  // 缩放级别
  7.     show-location/>    // 显示带有方向的小圆点
  8. </view>
复制代码

{{...}} 里面是模板数据,待会会在index.js里的data对象里面定义。

初始化模板数据,在index.js的data对象里添加如下代码:
  1. //index.js
  2. Page({
  3.   data: {
  4.     scale: 18, // 缩放级别,默认18,数值在0~18之间
  5.     latitude: 0, // 给个默认值
  6.     longitude: 0 // 给个默认值
  7.   },
  8.   onLoad: function(options){
  9.     // 1.页面初始化 options为页面跳转所带来的参数
  10.   },
  11.   onReady: function(){
  12.     // 页面渲染完成
  13.   },
  14.   onShow: function(){
  15.     // 页面显示
  16.   },
  17.   onHide: function(){
  18.     // 页面隐藏
  19.   },
  20.   onUnload: function(){
  21.     // 页面关闭
复制代码

这样我们的地图就默认中心位置为经度 0,纬度0。当然可能在开发者工具里显示不出来(有bug)。莫慌!因为这不是我们想要的,我们想要的是我们自己的位置,所以得先获取我们当前所在位置的经纬度,在index.js里的onLoad方法里添加如下代码:
  1. onLoad: function(options){
  2.   // 1.页面初始化 options为页面跳转所带来的参数
  3.   
  4.   // 2.调用wx.getLocation系统API,获取并设置当前位置经纬度
  5.     wx.getLocation({
  6.       type: "gcj02", // 坐标系类型
  7.       // 获取经纬度成功回调
  8.       success: (res) => { // es6 箭头函数,可以解绑当前作用域的this指向,使得下面的this可以绑定到Page对象
  9.         this.setData({  // 为data对象里定义的经纬度默认值设置成获取到的真实经纬度,这样就可以在地图上显示我们的真实位置
  10.           longitude: res.longitude,
  11.           latitude: res.latitude
  12.         })
  13.       }
  14.     });
复制代码

tips:res是一参数对象,它是由调用的对应API传过来的,如果你想知道res的具体值,可以在成功回调函数里打印:console.log(res),然后在控制台输出,看看它包含什么内容。在调用一个陌生API的时候可以用这种方法查看返回的对象数据,对处理逻辑很有帮助。
我们在地图上显示了我们的真实位置,但没有移动到页面的中心位置,微信提供的wx.moveToLocation()函数可以把当前位置移动到地图中心。修改index.js:
  1. //index.js
  2. Page({
  3.   data: {
  4.     scale: 18,
  5.     latitude: 0,
  6.     longitude: 0
  7.   },
  8. // 页面加载
  9.   onLoad: function(options){
  10.   // 1.页面初始化 options为页面跳转所带来的参数
  11.   
  12.   // 2.调用wx.getLocation系统API,获取并设置当前位置经纬度
  13.     wx.getLocation({
  14.       type: "gcj02", // 坐标系类型
  15.       // 获取经纬度成功回调
  16.       success: (res) => { // es6 箭头函数,可以解绑当前作用域的this指向,使得下面的this可以绑定到Page对象
  17.         this.setData({  // 为data对象里定义的经纬度默认值设置成获取到的真实经纬度,这样就可以在地图上显示我们的真实位置
  18.           longitude: res.longitude,
  19.           latitude: res.latitude
  20.         })
  21.       }
  22.     });
  23. }
  24. // 页面显示
  25.   onShow: function(){
  26.     // 1.创建地图上下文,移动当前位置到地图中心
  27.     this.mapCtx = wx.createMapContext("ofoMap"); // 地图组件的id
  28.     this.movetoPosition()
  29.   },
  30. // 定位函数,移动位置到地图中心
  31.   movetoPosition: function(){
  32.     this.mapCtx.moveToLocation();
  33.   }
  34. })
复制代码

我们在onShow函数里为地图组件(id为ofoMap)创建了一个地图上下文,调用了自定义函数movetoPosition(),而在自定义函数里又调用了moveToLocation这个API,这样,页面一显示就在地图中心显示当前位置。

我们地图上定位了我们的真实位置,但地图组件还是默认样式(点击调试,右侧面板点击wxml可以查看地图组件的默认格式),所以我们为地图组件添加样式,在index.wxss里编写样式:
  1. /**index.wxss**/
  2. .container{
  3.   position: relative;
  4.   width: 100%; // 宽度占满设备
  5.   height: 100vh; // 高度占满设备
  6. }
  7. #ofoMap{
  8.   position: absolute;
  9.   left: 0;
  10.   top: 0;
  11.   right: 0;
  12.   bottom: 0;
  13.   width: 100%;
  14.   height: 100%;
复制代码

保存刷新,整个屏幕就都显示地图了>_<

我们再来添加地图上的按钮
其实这里的按钮不是真正的按钮,它们属于地图上的控件属性,并且不随着地图移动。这里有一个坑:
地图组件属于微信原生组件,层级最高,任何元素都不能在地图之上显示,即无论设置多大z-index都无法显示。所以,要想在地图上添加按钮来满足需求,就要用到地图控件属性。

要添加地图控件,先在地图组件里声明controls="...":
  1. <!--index.wxml-->
  2. <view class="container">
  3.   <map id="ofoMap"
  4.     latitude="{{latitude}}"  // 纬度
  5.     longitude="{{longitude}}"  // 经度
  6.     scale="{{scale}}"  // 缩放级别
  7.     controls="{{controls}}" // 地图控件数组,多个控件存放在数组里
  8.     show-location/>    // 显示带有方向的小圆点
  9. </view>
复制代码

然后在index.js设置controls(看代码注释了解具体用法)
  1. //index.js
  2. Page({
  3.   data: {
  4.     scale: 18,
  5.     latitude: 0,
  6.     longitude: 0
  7.   },
  8. // 页面加载
  9.   onLoad: function(options){
  10.   // 1.页面初始化 options为页面跳转所带来的参数
  11.   
  12.   // 2.调用wx.getLocation系统API,获取并设置当前位置经纬度
  13.     ...已省略
  14.    
  15.   // 3.设置地图控件的位置及大小,通过设备宽高定位
  16.     wx.getSystemInfo({ // 系统API,获取系统信息,比如设备宽高
  17.       success: (res) => {
  18.         this.setData({
  19.           // 定义控件数组,可以在data对象初始化为[],也可以不初始化,取决于是否需要更好的阅读
  20.           controls: [{
  21.             id: 1, // 给控件定义唯一id
  22.             iconPath: '/images/location.png', // 控件图标
  23.             position: { // 控件位置
  24.               left: 20, // 单位px
  25.               top: res.windowHeight - 80, // 根据设备高度设置top值,可以做到在不同设备上效果一致
  26.               width: 50, // 控件宽度/px
  27.               height: 50 // 控件高度/px
  28.             },
  29.             clickable: true // 是否可点击,默认为true,可点击
  30.           },
  31.           {
  32.             id: 2,
  33.             iconPath: '/images/use.png',
  34.             position: {
  35.               left: res.windowWidth/2 - 45,
  36.               top: res.windowHeight - 100,
  37.               width: 90,
  38.               height: 90
  39.             },
  40.             clickable: true
  41.           },
  42.           {
  43.             id: 3,
  44.             iconPath: '/images/warn.png',
  45.             position: {
  46.               left: res.windowWidth - 70,
  47.               top: res.windowHeight - 80,
  48.               width: 50,
  49.               height: 50
  50.             },
  51.             clickable: true
  52.           },
  53.           {
  54.             id: 4,
  55.             iconPath: '/images/marker.png',
  56.             position: {
  57.               left: res.windowWidth/2 - 11,
  58.               top: res.windowHeight/2 - 45,
  59.               width: 22,
  60.               height: 45
  61.             },
  62.             clickable: false
  63.           },
  64.           {
  65.             id: 5,
  66.             iconPath: '/images/avatar.png',
  67.             position: {
  68.               left: res.windowWidth - 68,
  69.               top: res.windowHeight - 155,
  70.               width: 45,
  71.               height: 45
  72.             },
  73.             clickable: true
  74.           }]
  75.         })
  76.       }
  77.     });
  78. }
  79. // 页面显示
  80.   onShow: function(){
  81.     ...已省略
  82.   },
  83. // 定位函数,移动位置到地图中心
  84.   movetoPosition: function(){
  85.     this.mapCtx.moveToLocation();
  86.   }
  87. })
复制代码

tips: 我们在给控件设置top,left值时,用到了设备宽高。这样的话我们的控件位置就会在所有不同比例的设备上显示相同的效果了

我们再为地图控件绑定事件

现在地图上总共有四个图标可点击(地图中心的标记控件不需要点击,是用来指示中心位置的),所有我们需要为每个控件绑定不同的事件以实现不同的功能:
  • 点击定位控件,定位当前位置到地图中心,因为用户在拖动地图,有时需要回到当前所在位置。
  • 点击立即用车控件,调用微信内置扫码功能。然后获取开锁密码。
  • 点击举报按钮,前往维修报障页面。
  • 点击用户头像按钮,前往登录页面进行登录,查看余额,充值等操作

为控件绑定事件,同样需要在地图控件进行声明:
  1. <!--index.wxml-->
  2. <view class="container">
  3.   <map id="ofoMap"
  4.     latitude="{{latitude}}"  // 纬度
  5.     longitude="{{longitude}}"  // 经度
  6.     scale="{{scale}}"  // 缩放级别
  7.     controls="{{controls}}" // 地图控件数组,多个控件存放在数组里
  8.     bindcontroltap="bindcontroltap" // 控件点击事件
  9.     show-location/>    // 显示带有方向的小圆点
  10. </view>
复制代码


注意: bindcontroltap事件会响应所有控件的点击,所以,我们需要根据控件id来区分控件,然后响应不同的事件。这就是为什么要给控件设置id。

在index.js添加bindcontroltap事件:
  1. //index.js
  2. Page({
  3.   data: {
  4.     scale: 18,
  5.     latitude: 0,
  6.     longitude: 0
  7.   },
  8. // 页面加载
  9.   onLoad: function(options){
  10.   // 1.获取定时器,用于判断是否已经在计费
  11.     this.timer = options.timer;
  12.    
  13.   // 2.调用wx.getLocation系统API,获取并设置当前位置经纬度
  14.     ...已省略
  15.    
  16.   // 3.设置地图控件的位置及大小,通过设备宽高定位
  17.     ...已省略
  18. }
  19. // 地图控件点击事件
  20.   bindcontroltap: function(e){
  21.     // 判断点击的是哪个控件 e.controlId的值是当前点击控件的id。
  22.     switch(e.controlId){
  23.       // 点击定位控件
  24.       case 1: this.movetoPosition();
  25.         break;
  26.       // 点击立即用车,判断当前是否正在计费,此处只需要知道是调用扫码,后面会讲到这里的判断条件
  27.       case 2: if(this.timer === "" || this.timer === undefined){
  28.           // 没有在计费就扫码
  29.           wx.scanCode({
  30.             success: (res) => {
  31.               // 正在获取密码通知
  32.               wx.showLoading({
  33.                 title: '正在获取密码',
  34.                 mask: true // 显示蒙层
  35.               })
  36.               // 请求服务器获取密码和车号
  37.               wx.request({
  38.                 url: 'https://www.easy-mock.com/mock/59098d007a878d73716e966f/ofodata/password',
  39.                 data: {},
  40.                 method: 'GET',
  41.                 success: function(res){
  42.                   // 请求密码成功隐藏等待框
  43.                   wx.hideLoading();
  44.                   // 携带密码和车号跳转到密码页
  45.                   wx.redirectTo({
  46.                     url: '../scanresult/index?password=' + res.data.data.password + '&number=' + res.data.data.number,
  47.                     success: function(res){
  48.                       wx.showToast({
  49.                         title: '获取密码成功',
  50.                         duration: 1000
  51.                       })
  52.                     }
  53.                   })           
  54.                 }
  55.               })
  56.             }
  57.           })
  58.         // 当前已经在计费就回退到计费页
  59.         }else{
  60.           wx.navigateBack({
  61.             delta: 1
  62.           })
  63.         }  
  64.         break;
  65.       // 点击保障控件,跳转到报障页
  66.       case 3: wx.navigateTo({
  67.           url: '../warn/index'
  68.         });
  69.         break;
  70.       // 点击头像控件,跳转到个人中心
  71.       case 5: wx.navigateTo({
  72.           url: '../my/index'
  73.         });
  74.         break;
  75.       default: break;
  76.     }
  77.   },
  78. // 页面显示
  79.   onShow: function(){
  80.     ...已省略
  81.   },
  82. // 定位函数,移动位置到地图中心
  83.   movetoPosition: function(){
  84.     this.mapCtx.moveToLocation();
  85.   }
  86. })
复制代码

这里用到的API:
扫码API: wx.scanCode({})
显示加载框: wx.showLoading()
隐藏加载框: wx.hideLoading()
显示提示框: wx.showToast()
隐藏提示框: wx.hideToast()
向服务器发送请求:wx.request({})
关闭当前页面,跳转到指定页面: wx.redirectTo({})
保留当前页面,跳转到指定页面: wx.navigateTo({})
回退到指定页面: wx.naivgateBack({})

查看详细用法,查看官方API文档

tips:  跳转页面传参示例:
  1. let num = 1;
  2. wx.navigateTo({
  3.     url: '../other/index?num=' + num
  4. });
  5. // other页面onLoad接受参数
  6. onLoad: function(options){
  7.     console.log(options.num); // 1
  8. }
  9. // 多个参数用&分隔,如 'index?num=' + num + '&text=' + 'text'
  10.  
复制代码

我们现在在地图上添加单车标记makers和位置连线,还是在地图组件里先声明:
  1. <!--index.wxml-->
  2. <view class="container">
  3.   <map id="ofoMap"
  4.     latitude="{{latitude}}"  // 纬度
  5.     longitude="{{longitude}}"  // 经度
  6.     scale="{{scale}}"  // 缩放级别
  7.     controls="{{controls}}" // 地图控件数组,多个控件存放在数组里
  8.     bindcontroltap="bindcontroltap" // 控件点击事件
  9.     polyline="{{polyline}}"  // 位置连线
  10.     markers="{{markers}}" // 标记数组
  11.     bindmarkertap="bindmarkertap" // 标记点击事件
  12.     show-location/>    // 显示带有方向的小圆点
  13. </view>
复制代码

然后在index.js里定义:
  1. //index.js
  2. Page({
  3.   data: {
  4.     scale: 18,
  5.     latitude: 0,
  6.     longitude: 0
  7.   },
  8. // 页面加载
  9.   onLoad: function(options){
  10.   // 1.获取定时器,用于判断是否已经在计费
  11.     this.timer = options.timer;
  12.    
  13.   // 2.调用wx.getLocation系统API,获取并设置当前位置经纬度
  14.     ...已省略
  15.    
  16.   // 3.设置地图控件的位置及大小,通过设备宽高定位
  17.     ...已省略
  18.    
  19.   // 4.请求服务器,显示附近的单车,用marker标记
  20.     wx.request({
  21.       url: 'https://www.easy-mock.com/mock/59098d007a878d73716e966f/ofodata/biyclePosition',
  22.       data: {},
  23.       method: 'GET',
  24.       // header: {}, // 设置请求的 header
  25.       success: (res) => {
  26.           this.setData({
  27.             markers: res.data.data
  28.           })
  29.       }
  30.     })
  31. }
  32. // 地图控件点击事件
  33.   bindcontroltap: function(e){
  34.     ...已省略
  35.   },
  36. // 地图标记点击事件,连接用户位置和点击的单车位置
  37.   bindmarkertap: function(e){
  38.     let _markers = this.data.markers; // 拿到标记数组
  39.     let markerId = e.markerId; // 获取点击的标记id
  40.     let currMaker = _markers[markerId]; // 通过id,获取当前点击的标记
  41.     this.setData({
  42.       polyline: [{
  43.         points: [{ // 连线起点
  44.           longitude: this.data.longitude,
  45.           latitude: this.data.latitude
  46.         }, { // 连线终点(当前点击的标记)
  47.           longitude: currMaker.longitude,
  48.           latitude: currMaker.latitude
  49.         }],
  50.         color:"#FF0000DD", // 连线颜色
  51.         width: 1, // 连线宽度
  52.         dottedLine: true // 虚线
  53.       }],
  54.       scale: 18
  55.     })
  56.   },
  57. // 页面显示
  58.   onShow: function(){
  59.     ...已省略
  60.   },
  61. // 定位函数,移动位置到地图中心
  62.   movetoPosition: function(){
  63.     this.mapCtx.moveToLocation();
  64.   }
  65. })
复制代码

我在easy-mock上伪造了一些单车的经纬度,是基于我自己的位置。你可以在你周围设置经纬度,使用easy-mock伪造。可以看一下我的数据格式,单车经纬度接口

接下来我们为用户拖动地图绑定事件
我们已经为地图控件和标记响应了不同的事件,现在如果用户拖动地图,我们需要在拖动附近显示单车,先在地图组件声明地图拖动事件:
  1. <!--index.wxml-->
  2. <view class="container">
  3.   <map id="ofoMap"
  4.     latitude="{{latitude}}"  // 纬度
  5.     longitude="{{longitude}}"  // 经度
  6.     scale="{{scale}}"  // 缩放级别
  7.     controls="{{controls}}" // 地图控件数组,多个控件存放在数组里
  8.     bindcontroltap="bindcontroltap" // 控件点击事件
  9.     polyline="{{polyline}}"  // 位置连线
  10.     markers="{{markers}}" // 标记数组
  11.     bindmarkertap="bindmarkertap" // 标记点击事件
  12.     bindregionchange="bindregionchange" // 拖动地图事件
  13.     show-location/>    // 显示带有方向的小圆点
  14. </view>
  15.  
复制代码

然后在index.js里实现这个事件方法:
  1. //index.js
  2. Page({
  3.   data: {
  4.     scale: 18,
  5.     latitude: 0,
  6.     longitude: 0
  7.   },
  8. // 页面加载
  9.   onLoad: function(options){
  10.   // 1.获取定时器,用于判断是否已经在计费
  11.     this.timer = options.timer;
  12.    
  13.   // 2.调用wx.getLocation系统API,获取并设置当前位置经纬度
  14.     ...已省略
  15.    
  16.   // 3.设置地图控件的位置及大小,通过设备宽高定位
  17.     ...已省略
  18.    
  19.   // 4.请求服务器,显示附近的单车,用marker标记
  20.     ...已省略
  21. }
  22. // 地图控件点击事件
  23.   bindcontroltap: function(e){
  24.     ...已省略
  25.   },
  26. // 拖动地图事件
  27.   bindregionchange: function(e){
  28.     // 拖动地图,获取附件单车位置
  29.     if(e.type == "begin"){
  30.       wx.request({
  31.         url: 'https://www.easy-mock.com/mock/59098d007a878d73716e966f/ofodata/biyclePosition',
  32.         data: {},
  33.         method: 'GET',
  34.         success: (res) => {
  35.           this.setData({
  36.             _markers: res.data.data
  37.           })
  38.         }
  39.       })
  40.     // 停止拖动,显示单车位置
  41.     }else if(e.type == "end"){
  42.         this.setData({
  43.           markers: this.data._markers
  44.         })
  45.     }
  46.   },
  47. // 地图标记点击事件,连接用户位置和点击的单车位置
  48.   bindmarkertap: function(e){
  49.     ...已省略
  50.   },
  51. // 页面显示
  52.   onShow: function(){
  53.     ...已省略
  54.   },
  55. // 定位函数,移动位置到地图中心
  56.   movetoPosition: function(){
  57.     this.mapCtx.moveToLocation();
  58.   }
  59. })
复制代码


至此,首页地图已经完成了,下一节我们要编写响应的跳转页面

其他章节
微信小程序开发之OFO共享单车——扫码
微信小程序开发之OFO共享单车——单车报障页
微信小程序开发之OFO共享单车——个人中心页
微信小程序开发之OFO共享单车——钱包与充值

上一篇:微信小程序教程入门篇【6】,无编程经验微信小程序基本知识点
下一篇:微信小程序开发之OFO共享单车,微信小程序扫描二维码
相关资讯 Releva ntnews
解决方案 Solutions
相关热点 Hot spot
郑州快速建站:网站的站内优化怎么做
  1. 我们的优势
  2. 我们的实力
  3. 选择我们的理由
咨询电话(微信同号)

177-371-24501(同微信)

豫ICP备17049932号

Copyright © 2017-2022 版权所有 追风建站 Rights Reserved 技术支持:酷微科技

电话咨询 在线咨询 服务项目 SEO优化