Uni-App-03

登录功能开发

实现POST提交

HTTP协议规定请求消息内容类型(Content-Type)有哪些?------ 只有四种

text/plain 没有编码的普通数据

application/x-www-form-urlencoded 编码后的普通数据

multipart/form-data 请求主体中包含文件上传域

application/json 请求主体是 JSON 格式字符串
HTTP协议规定响应消息内容类型(Content-Type)有哪些?------ 有很多种

text/html、

text/plain、

text/css、

application/javascript、

image/jpeg、

application/mpeg3、

application/json、

...

html 复制代码
<script>
	export default {
		data(){
			return {
				statusBarHeight: 0,		//系统状态栏高度
				hidePwd: true,			//是否隐藏密码
				phone: "13501234567",	//用户输入的登录手机号
				pwd:"123456",			//用户输入的登录密码
			}
		},
		//生命周期方法 ------ 页面挂载完成
		onLoad(){    //此处此方法类似于mounted()
			//console.log('login组件挂载完成')
			//获取系统信息,读取其中的"状态栏高度"
			let {statusBarHeight} = uni.getSystemInfoSync()
			// console.log('屏幕高度:', screenHeight);
			this.statusBarHeight = statusBarHeight
		},
		methods: {
			async doLogin(){
				//console.log('当前输入:',this.phone, this.pwd)
				//1.验证手机号是否合法,不合法就弹出提示框,退出执行
				if(!/^1[3-9]\d{9}$/.test(this.phone)){
					uni.showToast({
						title: '手机号非法',		//提示标题
						icon: 'none',			//图标
						duration: 3000			//持续时长 
					})
					return
				}
				//2.验证密码是否合法,不合法就弹出提示框,退出执行
				if(this.pwd.length < 6){
					uni.showToast({
						title:'密码格式非法',
						icon: 'none',
						duration: 3000
					})
					return 
				}
				//3.把手机号/密码提交给服务器端数据API,进行登录验证
				/**********使用uni.request()发起POST请求************/
				let url = "https://www.codeboy.com/zhsqapi/user/login"
				let [err, res] = await uni.request({ 
					url,
					method: 'POST',
					//header: { 'Content-Type': 'application/json'},
					header: { }, //请求内容类型默认就是JSON格式
					//data: `{"phone":"${this.phone}", "pwd":"${this.pwd}"}`
					//JSON系列化:把普通的JS对象转换为JSON格式的字符串
					//data: JSON.stringify( {phone:this.phone, pwd:this.pwd})
					//uni.request方法会自动根据请求内容类型,把数据转换为需要的格式
					data: {phone:this.phone, pwd:this.pwd}
				})
				if(err){
					console.log('执行失败:', err);
				}else {
					console.log('异步请求成功:', res);
				}
				//4.登录成功,提示"欢迎回来",跳转到首页
			},
		}
	}
</script>

实现页面跳转

吐司对话框------ 用于简单的提醒,非重要事件的提示

​ uni.showToast( {title, icon, duration} )

模态对话框 ------ 用于严重的提示,甚至必须作出选择的提示

​ uni.showModal({title, content })

加载中对话框------ 提示操作正在进行中

​ uni.showLoading( ) / uni.hideLoading()

动作清单对话框------ 让用户选择要执行的动作

​ uni.showActionSheet( )
导航跳转:、 uni.navigateTo( )

导航返回跳转:、 uni.navigateBack( )

重定向跳转:、 uni.redirectTo( )

切换页签跳转:、 uni.switchTab ( )

重启跳转:、 uni.reLaunch( )

html 复制代码
<script>
	export default {
		data(){
			return {
				statusBarHeight: 0,		//系统状态栏高度
				hidePwd: true,			//是否隐藏密码
				phone: "13501234567",	//用户输入的登录手机号
				pwd:"123456",			//用户输入的登录密码
			}
		},
		//生命周期方法 ------ 页面挂载完成
		onLoad(){    //此处此方法类似于mounted()
			//console.log('login组件挂载完成')
			//获取系统信息,读取其中的"状态栏高度"
			let {statusBarHeight} = uni.getSystemInfoSync()
			// console.log('屏幕高度:', screenHeight);
			this.statusBarHeight = statusBarHeight
		},
		methods: {
			async doLogin(){
				//console.log('当前输入:',this.phone, this.pwd)
				//1.验证手机号是否合法,不合法就弹出提示框,退出执行
				if(!/^1[3-9]\d{9}$/.test(this.phone)){
					uni.showToast({
						title: '手机号非法',		//提示标题
						icon: 'none',			//图标
						duration: 3000			//持续时长 
					})
					return
				}
				//2.验证密码是否合法,不合法就弹出提示框,退出执行
				if(this.pwd.length < 6){
					uni.showToast({
						title:'密码格式非法',
						icon: 'none',
						duration: 3000
					})
					return 
				}
				//3.把手机号/密码提交给服务器端数据API,进行登录验证
				/**********使用uni.request()发起POST请求************/
				let url = "https://www.codeboy.com/zhsqapi/user/login"
				let [err, res] = await uni.request({ 
					url,
					method: 'POST',
					//header: { 'Content-Type': 'application/json'},
					header: { }, //请求内容类型默认就是JSON格式
					//data: `{"phone":"${this.phone}", "pwd":"${this.pwd}"}`
					//JSON系列化:把普通的JS对象转换为JSON格式的字符串
					//data: JSON.stringify( {phone:this.phone, pwd:this.pwd})
					//uni.request方法会自动根据请求内容类型,把数据转换为需要的格式
					data: {phone:this.phone, pwd:this.pwd}
				})
				if(err){
					console.log('执行失败:', err);
				}else {
					console.log('异步请求成功:', res);
				}
				//4.登录成功,提示"欢迎回来",跳转到首页
				//跳转到"首页" ------ 切换页签
                uni.showToast({
                        title: '欢迎回来',
                        icon: 'none',			//图标:不要
                        duration: 3000,			//持续时间:3s
                        complete(){				//对话框成功关闭
                        //跳转到"首页" ------ 切换页签
                        uni.switchTab({
                            url:'/pages/index/index'
                        })
                    }
                })
			},
		}
	}
</script>

Ajax集中管理

因为ajax访问在应用开发中频繁使用、代码复杂,且接口地址、数据结构等可能会发生变化

所以将ajax相关代码提取为方法,统一保存管理在外部文件中

创建service/index.js

js 复制代码
/****对服务器端数据API进行访问二次封装****/

/**
 * 服务器端基础地址
 */
export let base = 'https://www.codeboy.com/zhsqapi/'

/**
 * API-1.2、用户登录
 * 接口地址:user/login
 * 请求方式:POST
 * 请求主体格式:application/json   					
 * 	名称		必填		类型		说明
 *	phone	是		string	手机号
 *	pwd		是		string	密码
 */
export let userLogin = async (phone, pwd)=>{
	//1.准备请求URL
	let url = base + 'user/login'
	//console.log(url)
	//2.显示"加载中"提示框
	uni.showLoading({
		title: '用户登录中'
	})
	//3.发起异步请求消息
	let [err, res] = await uni.request({
		url,
		method: 'POST',
		data: {phone, pwd}
	})
	//4.隐藏"加载中"提示框
	uni.hideLoading()
	//5.返回响应消息主体
	return res.data
}
html 复制代码
<script>
	import { userLogin } from '../../service/'
	export default {
		data(){
			return {
				statusBarHeight: 0,		//系统状态栏高度
				hidePwd: true,			//是否隐藏密码
				phone: "13501234567",	//用户输入的登录手机号
				pwd:"123456",			//用户输入的登录密码
			}
		},
		//生命周期方法 ------ 页面挂载完成
		onLoad(){    //此处此方法类似于mounted()
			//console.log('login组件挂载完成')
			//获取系统信息,读取其中的"状态栏高度"
			let {statusBarHeight} = uni.getSystemInfoSync()
			// console.log('屏幕高度:', screenHeight);
			this.statusBarHeight = statusBarHeight
		},
		methods: {
			async doLogin(){
				//console.log('当前输入:',this.phone, this.pwd)
				//1.验证手机号是否合法,不合法就弹出提示框,退出执行
				if(!/^1[3-9]\d{9}$/.test(this.phone)){
					uni.showToast({
						title: '手机号非法',		//提示标题
						icon: 'none',			//图标
						duration: 3000			//持续时长 
					})
					return
				}
				//2.验证密码是否合法,不合法就弹出提示框,退出执行
				if(this.pwd.length < 6){
					uni.showToast({
						title:'密码格式非法',
						icon: 'none',
						duration: 3000
					})
					return 
				}
				//3.把手机号/密码提交给服务器端数据API,进行登录验证
				let data = await userLogin(this.phone, this.pwd)
				console.log(data)
				//4.登录成功,提示"欢迎回来",跳转到首页
				if(data.code===2000){  	//登录成功
					//弹出一个"吐司"对话框
					uni.showToast({
						title: '欢迎回来',
						icon: 'none',			//图标:不要
						duration: 3000,			//持续时间:3s
						complete(){				//对话框成功关闭
							//跳转到"首页" ------ 切换页签
							uni.switchTab({
								url:'/pages/index/index'
							})
						}
					})
				}else {					//登录失败
					//弹出一个"模态"对话框
					uni.showModal({
						title: '错误',
						content: '登录失败!服务器返回消息:'+data.msg
					})
				}
			},
		}
	}
</script>

处理吐司弹窗

无法实现吐司弹窗结束后页面跳转

所以需要将吐司提示放置到目标页-index.vue中实现

login.vue

html 复制代码
<script>
	import { userLogin } from '../../service/'
	export default {
		data(){
			return {
				statusBarHeight: 0,		//系统状态栏高度
				hidePwd: true,			//是否隐藏密码
				phone: "13501234567",	//用户输入的登录手机号
				pwd:"123456",			//用户输入的登录密码
			}
		},
		//生命周期方法 ------ 页面挂载完成
		onLoad(){    //此处此方法类似于mounted()
			//console.log('login组件挂载完成')
			//获取系统信息,读取其中的"状态栏高度"
			let {statusBarHeight} = uni.getSystemInfoSync()
			// console.log('屏幕高度:', screenHeight);
			this.statusBarHeight = statusBarHeight
		},
		methods: {
			async doLogin(){
				//console.log('当前输入:',this.phone, this.pwd)
				//1.验证手机号是否合法,不合法就弹出提示框,退出执行
				if(!/^1[3-9]\d{9}$/.test(this.phone)){
					uni.showToast({
						title: '手机号非法',		//提示标题
						icon: 'none',			//图标
						duration: 3000			//持续时长 
					})
					return
				}
				//2.验证密码是否合法,不合法就弹出提示框,退出执行
				if(this.pwd.length < 6){
					uni.showToast({
						title:'密码格式非法',
						icon: 'none',
						duration: 3000
					})
					return 
				}
				//3.把手机号/密码提交给服务器端数据API,进行登录验证
				let data = await userLogin(this.phone, this.pwd)
				// console.log(data)
				//4.登录成功,提示"欢迎回来",跳转到首页
				if(data.code===2000){  	//登录成功
					//跳转到"首页" ------ 切换页签
					uni.switchTab({
						url:'/pages/index/index'
					})
				}
				}else {					//登录失败
					//弹出一个"模态"对话框
					uni.showModal({
						title: '错误',
						content: '登录失败!服务器返回消息:'+data.msg
					})
				}
			},
		}
	}
</script>

index.vue

js 复制代码
//生命周期方法:组件加载完成
async onLoad() {	
    //弹出"欢迎回来"提示框
    uni.showToast({
        title:'欢迎回来',
        icon: 'none',
        duration: 3000
    })
},

令牌机制

令牌概念

HTTP协议属于**"无状态协议"**------客户端发起一个HTTP请求,服务器返回一个HTTP响应,服务器不会记录客户端的任何信息。实际应用中,很多场景下需要服务器记录客户端访问信息:例如根据访问历史进行后续的推荐、主题选择、购物车...

实现这类效果可用的技术:Cookie、SessionStorage&LocalStorage、Session、Token
Token :令牌,用于证明客户端身份的机制。

**原理:服务器端把客户端的信息保存在一个对象中,**加密为一个定长字符串,发送给客户端;客户端保存起来;等到下次请求时,客户端可以再把加密字符串返回给服务器;服务器可以解密出其中的原始信息,从而进一步查询更多信息------类似于银行给客户端的"银行卡",其中存储着客户端的信息(加密存储,客户端是读不懂的),后续有些请求需要客户端出示此"银行卡"有些请求则不需要。

客户端 服务器
1、客户端发送简单请求,包含phone和pwd
2、服务器验证登录信息,成功后,把客户端信息保存在一个对象中,形如:{ 用户编号:123, 用户名:yaya, 登录时间:x年x月x日 xx:xx:xx, 登录过期时间:x年x月x日 xx:xx:xx .... }
3.服务器将上述对象加密为定长字符串(即token);随同响应消息一同返回给客户端: { code:2000, msg: 'login succ', token: '加密后的定长字符串' }
4、接收到响应消息,把其中的token保存在客户端 uni.setStorageSync('userToken', data.token)
5、从客户端读取之前保存的token let token=uni.getStorageSync('userToken')
6、发送请求消息,把token放在请求消息头中(与后端协商好的请求头) uni.request({ url, header:{ token: token } }) 生成的请求消息形如: GET /index/data HTTP1.1 token: ''加密后的字符串''
7、服务器接收到请求消息,从请求头中读取req.headers.token(即token),解密令牌,得到原始的令牌信息,即:{ 用户编号:123, 用户名:yaya 登录时间:x年x月x日 xx:xx:xx, 登录过期时间:x年x月x日 xx:xx:xx .... }
8、服务器根据用户信息查询数据库,将用户信息返回给客户端

本地存储

异步操作localStorage:

uni.setStorage(k, v, success(){})

uni.getStorage(k, success(){})

uni.removeStorage(k, success(){})

uni.clearStorage( success(){} )

同步操作localStorage:

uni.setStorageSync(k, v)

uni.getStorageSync(k)

uni.removeStorageSync(k)

uni.clearStorageSync( )

html 复制代码
<script>
	import { userLogin } from '../../service/'
	export default {
		data(){
			return {
				statusBarHeight: 0,		//系统状态栏高度
				hidePwd: true,			//是否隐藏密码
				phone: "13501234567",	//用户输入的登录手机号
				pwd:"123456",			//用户输入的登录密码
			}
		},
		//生命周期方法 ------ 页面挂载完成
		onLoad(){    //此处此方法类似于mounted()
			//console.log('login组件挂载完成')
			//获取系统信息,读取其中的"状态栏高度"
			let {statusBarHeight} = uni.getSystemInfoSync()
			// console.log('屏幕高度:', screenHeight);
			this.statusBarHeight = statusBarHeight
		},
		methods: {
			async doLogin(){
				//console.log('当前输入:',this.phone, this.pwd)
				//1.验证手机号是否合法,不合法就弹出提示框,退出执行
				if(!/^1[3-9]\d{9}$/.test(this.phone)){
					uni.showToast({
						title: '手机号非法',		//提示标题
						icon: 'none',			//图标
						duration: 3000			//持续时长 
					})
					return
				}
				//2.验证密码是否合法,不合法就弹出提示框,退出执行
				if(this.pwd.length < 6){
					uni.showToast({
						title:'密码格式非法',
						icon: 'none',
						duration: 3000
					})
					return 
				}
				//3.把手机号/密码提交给服务器端数据API,进行登录验证
				let data = await userLogin(this.phone, this.pwd)
				// console.log(data)
				//4.登录成功,提示"欢迎回来",跳转到首页
				if(data.code===2000){  	//登录成功
					//在客户端存储服务器返回的token(身份令牌)
					uni.setStorageSync('userToken', data.token)
					//跳转到"首页" ------ 切换页签
					uni.switchTab({
						url:'/pages/index/index'
					})
				}else {					//登录失败
					//弹出一个"模态"对话框
					uni.showModal({
						title: '错误',
						content: '登录失败!服务器返回消息:'+data.msg
					})
				}
			},
		}
	}
</script>

获取主页数据

service/index.js中提供后去主页数据方法

传输令牌 获取数据

js 复制代码
/**
 * API-2.1、首页数据
 * 接口地址:index/data
 * 请求方式:GET
 * 请求头部:token - 用户登录后保存在客户端的身份凭证
 */
export let indexData = async ( )=>{
	//1.准备请求URL
	let url = base + 'index/data'
	//2.显示"加载中"提示框	
	uni.showLoading({
		title: '首页数据读取中'
	})
	//3.发起异步请求消息
	let [err, res] = await uni.request({
		url,		//请求地址
		header: {	//请求头部-token(客户端身份令牌)
			token: uni.getStorageSync('userToken')
		}
	})
	//4.隐藏"加载中"提示框
	uni.hideLoading()
	//5.返回响应消息主体
	return res.data
}

index.vue在onLoad()中获取主页数据

js 复制代码
import { indexData, base } from '@/service'
js 复制代码
//生命周期方法:组件加载完成
async onLoad() {	
    //弹出"欢迎回来"提示框
    uni.showToast({
        title:'欢迎回来',
        icon: 'none',
        duration: 3000
    })
    //向服务器请求首页数据
    let data = await indexData()
    console.log(data)
},

生命周期方法

应用程序生命周期方法

整个应用程序的生命周期方法 ------ App.vue ------ 高仿微信小程序**

​ onLaunch():整个应用程序启动了

​ onShow():应用程序显示出来,例如:第一次启动完成、从其它应用切换会当前应用

​ onHide():应用程序隐藏起来了,例如:来电话了、用户点击桌面按钮

页面生命周期方法

页面的生命周期方法 ------ pages ------ 高仿微信小程序**

onLoad():当前页面挂载完成,功能类似于mounted

onShow():页面显示出来了,例如:第一次挂载完成、导航返回之前的页面

onReady():页面准备就绪了,每个页面此方法调用且仅调用一次------第一次调用onShow之后

onHide():页面隐藏起来了,例如:导航跳转到下一个页面

onUnload():当前页面完成卸载,功能类似于destroyed

onPageScroll():页面滚动了

onReachBottom():页面滚动到底部了

onPullDownRefresh():页面在顶部下拉刷新了

组件生命周期方法

组件的生命周期方法 ------ components ------ 高仿Vue.js

​ 创建时期:beforeCreate()created()

​ 挂载时期:beforeMount()mounted()

​ 更新时期:beforeUpdate()updated()

​ 销毁时期:beforeDestroy()destroyed()

主页开发

保存主页数据

html 复制代码
<script>
import { indexData, base } from '@/service'
	export default {
		data() {
			return {
				base,				//把服务器基础地址变量设置为数据属性
				carousels:[],		//轮播广告条目列表
				menuItems:[],		//当前用户选中的功能菜单列表
				activities:[],		//最新的社区活动列表
			}
		},
		//生命周期方法:组件加载完成
		async onLoad() {	
			//弹出"欢迎回来"提示框
			uni.showToast({
				title:'欢迎回来',
				icon: 'none',
				duration: 3000
			})
			//向服务器请求首页数据
			let data = await indexData()
			this.carousels = data.carousels		//轮播广告
			this.menuItems = data.menuItems		//功能菜单
			this.activities = data.activities	//社区活动
		},
		methods: {
			
		}
	}
</script>

轮播图实现

html 复制代码
<!-- F1: 轮播广告 -->
<!-- indicator-dots:是否显示"小圆饼"指示器 -->
<!-- autoplay:是否自动播放轮播广告 -->
<!-- interval:时间间隔,两个广告间停留时间 -->
<!-- duration:持续时长,一个广告的过渡动画持续时长 -->
<swiper :indicator-dots="true" :autoplay="true" :interval="2000" :duration="500">
    <swiper-item v-for="(c, i) in carousels" :key="i">
        <view class="swiper-item">
        	<image @click="jump(c.href)" :src="base + c.pic" mode="widthFix"/>
        </view>
    </swiper-item>
</swiper>
html 复制代码
<script>
import { indexData, base } from '@/service'
	export default {
		data() {
			return {
				base,				//把服务器基础地址变量设置为数据属性
				carousels:[],		//轮播广告条目列表
				menuItems:[],		//当前用户选中的功能菜单列表
				activities:[],		//最新的社区活动列表
			}
		},
		//生命周期方法:组件加载完成
		async onLoad() {	
			//弹出"欢迎回来"提示框
			uni.showToast({
				title:'欢迎回来',
				icon: 'none',
				duration: 3000
			})
			//向服务器请求首页数据
			let data = await indexData()
			this.carousels = data.carousels		//轮播广告
			this.menuItems = data.menuItems		//功能菜单
			this.activities = data.activities	//社区活动
		},
		methods: {
			jump(url){
				console.log(url)
				//导航跳转到指定页
				uni.navigateTo({ url })
			}
		}
	}
</script>
html 复制代码
<style scoped lang="scss">
	//提示:页面中可以使用标签选择器,但是组件中不能使用
	.swiper-item > image {
		width: 750rpx;
	}
</style>

宫格图实现

需要安装uni-grid组件

html 复制代码
<!-- F2: 功能菜单 -->
<!-- column:一行中默认显示几列 -->
<!-- showBorder:是否显示边框 -->
<!-- square:每个宫格项是否显示为方形 -->
<uni-grid class="func-menu" :column="4" :showBorder="false" :square="true">
    <uni-grid-item v-for="(item,i) in menuItems" :key="i">
        <view class="menu-item" @click="jump(item.href)">
            <image :src="base+item.pic" mode="widthFix"/>
            <text>{{item.title}}</text>
        </view>
    </uni-grid-item>
</uni-grid>
html 复制代码
<style scoped lang="scss">
    .func-menu {
        margin-top: $uni-spacing-col-base;
        background-color: $uni-bg-color;
        .menu-item {
            height: 100%;
            //把弹性容器的主轴方向修改为:纵向
            flex-direction: column;
            //弹性容器中的子元素在主轴方向上居中对齐
            justify-content: center;
            //弹性容器中的子元素交叉轴方向上居中对齐
            align-items: center;
            > image { width:35%; margin-bottom: $uni-spacing-col-sm; }
        }
    }
</style>

商业服务功实现

需要安装uni-card扩展组件

html 复制代码
<!-- F3: 商业服务 -->
<!-- isFull:是否显示为"通栏卡片"(左右撑满) -->
<uni-card class="card" title="| 商业服务" is-full>
	<view class="service">
		<!-- 左侧:房屋租售 -->
		<view class="service-item">
			<view class="txt">
				<text>房屋租售</text>
				<view>
					<navigator>租房</navigator>
					<navigator>短租</navigator>
				</view>
			</view>	
			<!-- 图片缩放模式1:widthFix -->
			<!-- 图片缩放模式2:scaleToFill:不保持原始宽高比,缩放图片填满指定宽高 -->
			<image class="img" mode="scaleToFill" src="../../static/img/chuzu.png"/>
		</view>
		<!-- 右侧:便民服务 -->
		<view class="service-item">
			<view class="txt">
				<text>便民服务</text>
				<view>
					<navigator>便利店</navigator>
					<navigator>超市</navigator>
				</view>
			</view>	
			<image class="img" mode="scaleToFill" src="../../static/img/bianmin.png"/>
		</view>
	</view>
</uni-card>
css 复制代码
.service {
	width: 100%;
	.service-item {
		//弹性子元素尺寸增长权重为:1
		flex: 1;
		padding-top: $uni-spacing-col-sm;
		padding-bottom: $uni-spacing-col-sm;
		justify-content: space-between; //弹性容器中的子元素在主轴方向上:空白在中央
		align-items: center; //弹性容器中的子元素在交叉轴方向上:居中对齐
		&:nth-child(1) {padding-right: $uni-spacing-row-sm;}
		&:nth-child(2) {padding-left: $uni-spacing-row-sm;}
		.txt { 
			font-size: $uni-font-size-sm; 
			flex-direction: column; 
			flex: 1; //弹性子元素尺寸增长权重:1
			navigator {margin-right: $uni-spacing-row-sm;}
		}
		.img { width: 150rpx; height: 120rpx;}
	}
}
相关推荐
x原力觉醒5 小时前
uniapp跨域问题,在开发环境中配置
javascript·vue.js·uni-app
小威编程6 小时前
uni-app应用级生命周期和页面级生命周期
前端·vue.js·uni-app
江-月*夜6 小时前
uniapp vuex 搭建
android·javascript·uni-app
圈圈的熊6 小时前
uniapp 使用 websocket
uni-app
计算机学姐11 小时前
基于uniapp微信小程序的旅游系统
vue.js·spring boot·mysql·算法·微信小程序·uni-app·旅游
计算机学姐11 小时前
基于uniapp微信小程序的宠物救助宠物领养系统
vue.js·spring boot·mysql·微信小程序·小程序·uni-app·宠物
大牛哥哥11 小时前
uni-app @click.stop @click.stop.native均不生效
javascript·vue.js·uni-app
赵锦川12 小时前
微信小程序 uniapp 腾讯地图的调用
微信小程序·小程序·uni-app
计算机学姐12 小时前
基于uniapp微信小程序的校园二手书交易系统
java·vue.js·spring boot·mysql·微信小程序·java-ee·uni-app
我有一个object20 小时前
uniapp的IOS证书申请(测试和正式环境)及UDID配置流程
javascript·ios·uni-app·vue