uni-app的开发经历

uni-app的开发经历公司需求要做app.微信小程序,支付宝小程序三端的东西,查阅了一些资料,最终选择用uni-app,以下是一些其他前辈技术选型的文章,各位可以学习一下。 其实我觉得自己的水平不能够去评价一款框架的好坏,只是简单的说一下开发体验,我认为坑大概分三种。 1:该框架确实没有的功能,存在…

技术选型

公司需求要做app.微信小程序,支付宝小程序三端的东西,查阅了一些资料,最终选择用uni-app,以下是一些其他前辈技术选型的文章,各位可以学习一下。

uni-app官方文档:

uniapp.dcloud.io/README

掘金上选型文章:

juejin.im/post/684490…

juejin.im/post/684490…

对比和参照之后,再结合我司情况,技术总监选择了uni-app

我认为的坑?

其实我觉得自己的水平不能够去评价一款框架的好坏,只是简单的说一下开发体验,我认为坑大概分三种。

1:该框架确实没有的功能,存在不能实现的东西。

2:明显的bug,导致程序出现问题,影响开发进度和结果

3:在开发过程中,自己凭借直觉和经验在开发,结果和预期不同,阅读文档过后才发现和之前经验积累的写法不一样,会不由自主的说一句真坑,或者说在不同端有的不同写法,但自己没有做好兼容,就出现了不同端效果不一致,当再次阅读文档找到问题后也会来一句真坑。

总的来讲,这次Uni-app的开发之旅还是比较通畅的。

踩坑之旅

前言:我只说我遇见过的问题难点,和一些注意点,以及怎么去解决,不会讲uni-app怎么用,怎么用大家还是去阅读文档吧,文档上写的更清楚

像素单位

使用upx/rpx 而不是 px

修改内容(评论区大佬的订正): 1px = 2upx是不准确的,upx和rpx是响应式单位,以750px为基准宽度,根据设备屏幕宽度自动调整

引入vuex

uni-app的开发经历 常规的引入方式在uniapp中是不行的,需要在mian.js中,vue原型上挂载一次

uni-app的开发经历

4月20日更新:评论区有朋友提出现在以这样的形式可以引入vuex了,我想是uniapp团队优化之后的内容

uni-app的开发经历

路由

uni-app的路由全部配置在page.json这个文件中,问题就在于多人开发的时候,路由无法拆分,如果处理的不好,经常发生冲突。至于其中的一些配置项,就请见官方文档。

在页面中没有专门的 $route$router对象 仅能在页面的生命周期里面接受路由传参,详情见文档。

uniapp.dcloud.io/frame?id=%E…

路由传参方式
let url = `/pages/shopManagement/sonPage/billDetails?StoreID=${StoreID}`

路由接参方式:

onLoad(route){
	this.getData.StoreID=route.StoreID
	this.getCurryInfo()
},
onLoad接收到一个参数对象

DOM操作

如果你的项目仅是h5,那可以放心大胆的使用dom操作,但如果要在小程序和app跑,就不要做dom操作了,不生效。

不过ref还是可以用的,一样可以获取到这个节点,该干啥干啥。 uni-app的开发经历

生命周期

说到ref我就要提一下生命周期

具体的生命周期在文档中可以看详情

uniapp.dcloud.io/frame?id=%E…

大致上和vue的差不多,分成页面生命周期和应用生命周期,页面生命周期就是针对单页面的,应用生命周期就是针对整个小程序/app的,不过我提出在开发时的一些情况

在组件中,没有生命周期,对,你没看错!比如页面a引用了组件b 在组件b中,onLoad,onShow,onReady全部失效,不过用created和mounted是生效的,但是我在开发的时候还是没有用created和mounted,毕竟文档明确写到

uni-app的开发经历

所以我在组件中规避使用原vue的生命周期,另外,在上面说了ref,如果要在初始化使用ref要注意生命周期,在onload和show的钩子中,内部如果是同步操作是用不了的,拿不到$refs,我不知道怎么解释这个问题,在vue中很好解释,在created拿不到ref是因为dom还没有渲染出来,只有在mounted时dom渲染出来了才能拿到ref,但是uniapp中不是没得dom嘛。。。。。我也没深究过,如果要用,只能异步,可以加setTimeout 或者 放在某个请求后用,这个时候是可以拿到ref的

uni-app的开发经历

201912.2更新

最近几个月又开了个新项目,赶得很紧,不过有了第一个app的基础,开发起来很顺手。

同时也在重构后台系统,有时候div和view傻傻分不清老是整错。

好了,言归正传,今日我在交流群里看见有朋友问起了组件内生命周期的问题,我也讲了自己的疑惑,我就直接贴图给大家看看。

uni-app的开发经历

也就是说其实在非页面组件里面使用vue自带的生命周期是可行的,之前我没有尝试过,所以一直规避了这种用法,把很多东西都放在页面的生命周期里面搞。

关于请求

我们最开始的时候是自己简单的封装了一下发送的请求

export const HttpRequest_ =  {
	config: function(name) {
		var info = null;
		if (name) {
			var name2 = name.split("."); //字符分割
			if (name2.length > 1) {
				info = configdata[name2[0]][name2[1]] || null;
			} else {
				info = configdata[name] || null;
			}
			if (info == null) {
				let web_config = cache.get("web_config");
				if (web_config) {
					if (name2.length > 1) {
						info = web_config[name2[0]][name2[1]] || null;
					} else {
						info = web_config[name] || null;
					}
				}
			}
		}
		return info;
	},
	post: function(url, data, header) {
		header = header || "application/x-www-form-urlencoded";
		//url = this.config("APIHOST")+url;
		return new Promise((succ, error) => {
			showLoading_()
			uni.request({
				url: url,
				data: data,
				method: "POST",
				header: {
					"content-type": header
				},
				success: function(result) {
					hidLoading_()
					succ.call(self, result.data)
				},
				fail: function(e) {
					hidLoading_()
					error.call(self, e)
				}
			})
		}).then(res=>{
				console.log(res)
				return res
			},err=>{
				console.log('err:',err)
		})
	},
	get: function(url, data, header) {
		header = header || "application/x-www-form-urlencoded";
		//url = this.config("APIHOST")+url;
		return new Promise((succ, error) => {
			showLoading_()// 加载中
			uni.request({
				url: url,
				data: data,
				method: "GET",
				header: {
					"content-type": header
				},
				success: function(result) {
					hidLoading_() //关闭加载中
					succ.call(self, result.data)
				},
				fail: function(e) {
					hidLoading_() //关闭加载中
					error.call(self, e)
				}
			})
		}).then(res=>{
			console.log(res)
			return res
		},err=>{
			uni.showToast({
				duration:2000,
				title:'数据异常,请稍后再试',
				icon:'none'
			})
			console.log('err:',err)
		})
	}
}

之前我以为在Uniapp中发送请求只能用他们请求方法,后来同事说也可以用其他的。

我们便引入其他的库。这是uniapp插件市场别人封装好的,用起来还是比较舒服

ext.dcloud.net.cn/plugin?id=5…

导航栏

导航栏注意的一个问题就是不同端的不同展示形式,所以需要处理兼容问题。

导航栏,可以用自定义的,可以用框架提供的,也有一些插件,都还是可以用的,就是注意显示的区别,如果有多端需求,一定要在不同端跑一下看看效果,不然到时候会很烦。我们一直用的colorUi的导航栏,自己稍微改动了一下

在支付宝小程序中,原生的导航栏是取消不了,所以不能用自定义的插件,就需要在page.json中配置原生的导航栏

随便找了一个页面,我们的头部导航是条件编译的: uni-app的开发经历

动态的class style

具体的文档在这里:

uniapp.dcloud.io/use

我一开始是没有注意到这一点,按照我的一些常规习惯写并且一直在用h5调试,没有任何问题,后来上真机和小程序开发工具之后才发现全部失效。

打开第三方的网址或app

在app端想要打开第三方的网址或者程序,一定要区分ios和安卓端。

首先,ios和安卓唤起第三方app的地址是不一样的。

uni-app的开发经历

不管是在调试还是打包,要唤起第三方程序,在ios端要配置白名单

uni-app的开发经历

三方登录

以微信登录位例:

在app端,uni-app集成的几个方法,可以很顺利的拿到unionId,openid等一些列信息

    uni.getProvider({//获取uniapp支持的第三方数据
      service:'oauth'
    }).then(data=>{
      var [err,res] = data
      var providers=res.provider//类型(微信,新浪,小米,qq)
      var flagIndex=providers.indexOf(provider)
      if(flagIndex>-1){
        return providers[flagIndex] /
      }
    }).then(res=>{
      return uni.login({//登陆接口(可以获取用户信息)
        provider:res,
        scopes:'auth_base',
        timeout:20000,
      })
    }).then(data=>{//返回一系列登陆信息
        var [err,res] = data
        if(res.errMsg==="login:ok"){
          self.authResult=res.authResult
          return res.authResult
        }
      }
    }).then(res=>{//获取用户的信息 头像,地址,等等等
      return uni.getUserInfo({
        provider:provider,
        timeout:20000,
        withCredentials:true
      })
    }).then(data=>{//得到一些列用户信息
      var [err,res] = data 
      console.log(res)
      if(res.errMsg==="getUserInfo:ok"){
        return res
      }
    })

但是如果在小程序端,很多方法就失效了,因为小程序有一套自己的三方登录交互策略。

developers.weixin.qq.com/miniprogram…

还记得当时刚在app上测成功微信三方登陆后,领导过来看进度,问小程序怎么样,我给他放了个体验版,让他看看,他问我这个微信登录也可以吗?我拍拍胸脯说没得问题,随便登,结果。。。。。。。。。。。。。。。脸疼

    uni.login({//登陆接口
      provider:'weixin',
      scopes:'auth_base',
      timeout:20000,
    }).then(data=>{//返回一系列登陆信息
        let [err,res] = data
        if(res.code){
          let data ={//这个code很重要,需要拿到code向后台去换unionid等
            js_code: res.code
          }
      return this.$Request.get(this.$store.state.getopenidUrl,data)
    }else{
      setTimeout(()=>{
        this.$api.msg('数据异常')
      },500)
      uni.switchTab({
        url:'/pages/index/index'
      })
    }
    }).then(res=>{
        let res_ = JSON.parse(res.Data)
        if('unionid' in res_){
          this.getIsBindData.openid=res_.unionid
          this.getDataWX.openid = res_.unionid
          this.getDataWX.unionid = res_.openid
        }else{
          this.getIsBindData.openid=res_.openid
          this.getDataWX.openid = res_.openid
          this.getDataWX.unionid = res_.openid
        }
        return this.WXuserInfo
    })

其他的三方登录我没有试过,但是一定要注意各端之间的差异性

另外,支付宝三方登录uni-app没有集成,要是自己想做,就用原生来写,理论上是可以做的。由于我们团队没有会原生的,我们试过用webview做支付宝的三方登录,最后还是卡在了授权这一块,不得而终,遂阉了该需求。

2019 8.20 更新:

在插件市场已经有了安卓端和ios端授权登录的插件(是付费插件)详情:

ext.dcloud.net.cn/plugin?id=6…

canvas

写成组件在小程序上失效,仅能在index页面上使用。在h5生效,封装成组件也可以,渲染效果也不是太好,app端没有试过,因为后来看效果不好就暂时搁置这个需求了。

评论区朋友的订正: canvas在组件中使用时,记得传递第二个参数组件实例对象:uni.createCanvasContext(canvasId, this)

我试过了,封装成组件的情况下,传入this,在小程序是生效的.之前是没有传入的

uni-app的开发经历

注意权限模块与sdk的配置

我之前从未写过app和小程序,对一些东西不够敏感

在我调试地图,导航等功能的时候用的很顺畅,后来打包就失效了,就是因为没配好,原来在我们调试的时候用的是Uniapp的。

另外需要用到一些权限的时候记得也要去manifest.json中去配置,总的来讲,图形化界面配置还是比较友好,源码配置也没有那么复杂,基本上都能查到

个人建议只要是真机调试就用自定义基座,尽量不要用uniapp给的基座,这样可以避免一些干扰,在标准基座没问题,一打包就出问题,用自定义基座会好一些。

nvue

weex魔幻的东西太多了,我就不一一列举了

比如weex中自带flex布局,但是排布顺序是竖着的,你说这个是不是头疼。

我用原生插件拼样式的时候真滴是痛苦,好几处觉得分分钟能解决的东西,半天搞不定,我当时还在想不会是记忆出错了?不是这么用的,还查了半天,结果依旧不生效,突然反应过来,别在weex里面就不是这么用,一看文档,果然如此!

个人建议先把weex的文档通读一下,或者用某个东西之前先去查查看怎么用。别一根筋去根据自己脑海里的css知识来对抗另一套标准的css,这样永远都对不准。

weex.apache.org/zh/docs/sty…

2019 8.20 更新:

评论区朋友补充: nvue要支持upx,需要使用uni-app编译模式,而不是weex编译模式

开屏引导

uni-app没得开屏这个配置项,只能用一些策略来做。

ext.dcloud.net.cn/plugin?id=1…

实质上是是用swiper组件+本地缓存做的模拟开屏引导,注意的是,如果产品一开始就定位了要做引导页,那就考虑好index的怎么写,我们是后期才打算加的,如果要把index改掉成本有点大,所以用了另一种策略,但如果在性能不好的手机上会出现尴尬的事情,就不细说怎么尴尬了,如果用上面的策略,再尴尬也不过是白屏,在上面的demo中,index仅是一个中转页面,什么都没有写。所以个人建议还是用以上的策略。

路径别名

至今我没有我没有找到如何配置路径别名的文件或者方法。

在uniapp中 @ 是表示根路径,不过整个文件结构还是很清晰的,没有那么多的配置文件,整个根文件很像常规的src目录,用@基本上也很舒服。

插件市场和生态

总的来讲,Uniapp的插件市场还是不错的,大多数能用到的插件都可以找到,找不到的也可以从个别相似的插件中找到灵感,自己再魔改一下,论坛也还行,基本上自己遇到的问题都能找到答案,但是也有找不到的或者有人提出却无人回答的,官方qq群也还是比较热闹,有些东西自己没遇见的看看别人的问题和解决方案也算是一种成长,自己遇见过的给别人解答,换来一声谢谢也是很开心的。

压缩图片

2019 8.16更新

文档:

uniapp.dcloud.io/api/media/i…

我不知道为什么 uni.compressImage(OBJECT) 这个接口在app端失效微信小程序生效,报错信息也很简单,就是说压缩失败,没有其他的提示,我在官方群问了一圈没人给我回答,看了下社区也没有太有价值的回答。

后来自己慢慢琢磨了一下,也参考了这位老哥的文章(文章中也包含了转换base64的方法),简单的封装了个函数。

参考文章:

github.com/SilurianYan…

/* src:图片的本地路径 quality:压缩范围 0-100 */

 function zipImage(src,quality=30){
	// #ifdef APP-PLUS app
	let index = src.lastIndexOf(".")
	let imgDirname = src.substring(0,index)//图片的原始地址
	let imgName = new Date().getTime();//压缩后的文件
	let imgType  = src.substring(index+1,src.length);//图片的类型
	return new Promise((resolve,rej)=>{
		plus.zip.compressImage({
			src,
			dst: imgDirname+imgName+'.'+imgType,
			quality,
		}, res => {
			resolve(res.target)
		}, err => {
			console.log(err);
			rej(err)
		});
	})
	// #endif
	// #ifdef MP-WEIXIN 微信小程序
	return new Promise((resolve,rej)=>{
		uni.compressImage({
		  src,
		  success:(res)=>{
		  	console.log(res,1)
			let path = res.tempFilePath
			resolve(path)
			// this.imgList.push(path)
		  	// this.imgList.push
		  },
		  fail:(e)=>{
			uni.showToast({
				title:'上传失败,请重新上传',
				duration:2000
			})
		  },
		  complete:(val)=>{
		  	console.log(val,3)
		  }
		})
	})
	// #endif
	
}

调用的时候,直接会吐出来压缩后的图片路径

uni.chooseImage({
	success:(res)=>{
	    res.tempFiles.forEach(async (it)=>{
			let localPath =it.path	
			localPath = await this.$api.zipImage(localPath) //await一下就可以接住了
			this.imageList.push({
				value:localPath
			})	
							
		})
	}
});					

20120 2.19更新

最近又写了两个uniapp的项目 基本上没踩什么坑

倒是加入了几个新功能,在这里稍微总结一下

第一个是app推送 第二个是语音播报 第三个是直播

app推送

ask.dcloud.net.cn/article/356…

这是官方的说明 按照配置配置结束以后 就可以开始测试了,但是测试之前需要拿到一个cid 拿cid的函数就是这个

    const clientInfo = plus.push.getClientInfo()
    let pushInfo = {
		clientid: clientInfo.clientid,
		appid: clientInfo.appid,
		appkey: clientInfo.appkey   
    };
    //这端代码可以拿到需要的一些信息,包括cid
    
    //拿到cid就可以开始发送测试的推送了

至于透传和通知栏信息的区别是什么,在百度或者谷歌一搜会有很多讲的详细的文章,比我这个前端菜鸡说的仔细的多,所以我就不多加赘述了。

在收到推送之前,也可以检测一下用户是否看起了推送权限,直接上代码吧

async function pushPower(phoneType){
		if(phoneType==='android'){//如果是安卓
			console.log('安卓')
			let main = plus.android.runtimeMainActivity();
			let pkName = main.getPackageName();  
			let NotificationManagerCompat plus.android.importClass("android.support.v4.app.NotificationManagerCompat");  
			let packageNames = NotificationManagerCompat.from(main);  
			// console.log(JSON.stringify(packageNames)); 
			if (packageNames.areNotificationsEnabled()) {  
					  console.log('已开启通知权限');  
			}else{  
			    /*这段函数是自己封装的弹框询问*/
				let res = await this.$api.showModal({
					title: '是否开启推送权限',
					content: '您还未开启推送权限,是否开启?不开启推送权限部分功能将不完善。'
				})
				 /*这段函数是自己封装的弹框询问*/
				
				if(res){
					let Intent = plus.android.importClass('android.content.Intent');
					let intent = neIntent('android.settings.APP_NOTIFICATION_SETTINGS');//可设置表中所有Action字段 
					intent.putExtra('android.provider.extra.APP_PACKAGE', pkName);  
					main.startActivity(intent);  
				} 
			}
		}else if(phoneType==='ios'){//如果是ios
			let UIApplication = plus.ios.import("UIApplication");  
			let app = UIApplication.sharedApplication();  
			let enabledTypes  = 0;  
			if (app.currentUserNotificationSettings) {  
					  let settings = app.currentUserNotificationSettings();  
					  enabledTypes = settings.plusGetAttribute("types");  
			} else {  
					  //针对低版本ios系统 
					  enabledTypes = app.enabledRemoteNotificationTypes();  
			}  
			plus.ios.deleteObject(app);  
			if ( 0 == enabledTypes ) {  
			     /*这段函数是自己封装的弹框询问*/
				let res = await this.$api.showModal({
					title: '是否开启推送权限',
					content: '您还未开启推送权限,是否开启?不开启推送权限部分功能将不完善。'
				})
				 /*这段函数是自己封装的弹框询问*/
				 
				 
				if(res){
					let UIApplication = plus.ios.import("UIApplication");
					let NSURL = plus.ios.import("NSURL");  
					let setting = NSURL.URLWithString("app-settings:");  
					let application = UIApplication.sharedApplication();  
					application.openURL(setting);  
					plus.ios.deleteObject(setting);  
					plus.ios.deleteObject(application);  
				} 
			}  
		}
	}

反正做不做权限检查也看各位的需求

最后一步就是监听推送信息了,在这里我就不放我的代码了,给大家提供一个文章,这个文章里面详细的撰写了怎么去监听推送等。

ask.dcloud.net.cn/article/366… 前端这块只需要看他的【8】 里面 对接收不同信息做了详细代码演示。

具体发透传信息还是通知栏信息,接到后怎么处理等,就看各位和后端怎么协商了。

语音播报

这个就很简单了。

ext.dcloud.net.cn/plugin?id=1…

就用这个插件,该配置的一配置 对着文档写一下 啥都出来了

直播

直播的话 我觉得大概分成两个方面

第一就是自己的后端自己的搭建推送服务器

1,app的推送:

可以看这位掘金朋友的文章,我当时是请教他了,给我讲了很,但是最后我们没有做自己的直播服务器用的第三方的方案

juejin.im/post/684490…

2,小程序的直播:

我只是看了下文档,后面没有做,他的拉流组件与app用的不是同一个

uniapp.dcloud.io/component/l…

第二是用现成的直播插件与服务,主要是腾讯云

1,app的直播方案:

我们是花钱买的原生插件,这位作者售后很好,有问必答,而且也是永久维护。

第一款上手比较快,但不算是真正意义的直播,是实时音视,不能做到录屏等功能。但是单纯的播放画面和接收声音是完全没有问题的,连麦,美颜,闭麦静音等都可以。具体的逻辑可以根据自己的业务来做,我们的业务主要是讲课软件,不仅要直播,也需要连麦的功能,这个就很符合我们的需求。

ext.dcloud.net.cn/plugin?id=8…

第二个款组件我没有用过,但是也放出来大家可以参考一下。

ext.dcloud.net.cn/plugin?id=1…

2,小程序的直播 目前我们没打算在小程序做直播,因为资格不够,但是我在看直播插件的时候,看了个很好的例子,放出来大家也可以看看做做参考。

ext.dcloud.net.cn/plugin?id=1…

他是把腾讯视频云小程序的功能做成了uniapp版本的,大家可以参考着来做。

说一点感受,就是uniapp对针对三方SDK的集成支持度不是很好,或者说第三方的sdk针对uniapp的sdk集成不是很好?

20120 3.2更新 :

App权限

昨日在打包的时候,发现我的摄像头权限从来没有被询问过是否使用,直接就给我反应说未授权,我琢磨是哪里没配置,我才去配置项里面找到并配置了,但是这些配置项很多也不太熟悉,索性在论坛里面搜索了很多相关的文章看了看。就发现一个好东西,是关于权限的判断和提示的。 uni-app的开发经历

上面推送的内容中,我写到了app推送权限的判断。我觉得还不如人家这个写得好,所以干脆我都改成了这个。

ext.dcloud.net.cn/plugin?id=5…

这里面不仅把权限名称都放出来了,就算自己配置打包,也可以拿来做参考,并且也封装成了模块,调用查询用户的功能权限非常的方便!

插件市场里面我看过or用过比较好的组件

先后推荐不分排名顺序

colorUI 我们四个项目,都是以colorUI做的基础样式,他不是一个组件库,是个css样式库,唯一的缺点就是没有太详细的文档,不过源码都在,过一遍基本上也都熟悉了。

ext.dcloud.net.cn/plugin?id=2…

===================================

uCharts 高性能跨全端图表,基本上所有类型的图表都包含了,而且文档也比较清楚,demo也全面 源码也可以根据自己的需求做改动,我自己在开发的时候也改动过源码,用起来很顺手。

ext.dcloud.net.cn/plugin?id=2…

===================================

手写签名组件,方便易操作。

ext.dcloud.net.cn/plugin?id=3…

===================================

答题模版,这个答题模板是基于colorUI做的,我当时自己懒得写了,就直接搬来用。

ext.dcloud.net.cn/plugin?id=4…

===================================

ThorUI组件库 这个库是个正儿八经的组件库,里面包含了常规的各种组件,我们也买了graceUI,其实对比一下,大体上不相上下的,有不同的需求也可以直接改源码啥的。

ext.dcloud.net.cn/plugin?id=5…

===================================

侧边导航 不过侧边导航也实现起来也比较简单,这个是我看到的一个组件,看了下源码,如果是拿来练手实现的话,是个比较好的参照对象。

ext.dcloud.net.cn/plugin?id=7…

===================================

O2O本地生活模版 这个模板用来快速开发电商项目很好,作者组件封装的很完善,有需求的只要自己改一下就可以用了。

ext.dcloud.net.cn/plugin?id=1…

===================================

mescroll的uni版本, 是在 uni-app 运行的下拉刷新和上拉加载的组件 第一个项目用到了他 不过后来我自己封装了一个就没用了。

www.mescroll.com/uni.html

===================================

基于flyio接口封装

ext.dcloud.net.cn/plugin?id=5…

===================================

腾讯云小程序音视频通讯Uniapp版

ext.dcloud.net.cn/plugin?id=1…

最后还是一句话

注意各端的差异性,很多东西,h5对着的,上真机就错了,真机好着的,换小程序就错了,不同小程序之间还有差异,总之就是考虑好不同的情况,重点是仔细阅读文档。

虽然可能一些原生可以实现的功能uniapp实现不了,不过整体开发下来还是比较愉快,很多的坑还是因为多端不兼容,除了写起来麻烦一点,基本上都还是有可以解决的策略。

也希望uniapp越做越好,各方面越来越完善,为我这种搬砖码农增加一份生存的筹码。

今天的文章uni-app的开发经历分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20952.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注