uniapp vue2 小程序如何获取unionid中如何配置unocss?

theme: smartbluehighlight: dark创建项目创建后的项目此处插入一个坑亦可以使用uniapp vue-cli 创建项目vue create -p dcloudio/uni-preset-vue my-project使用vue3/vite 创建项目(如命令行创建失败,请直接访问 gitee 下载模板)npx degit dcloudio/uni-preset-vue#vite my-vue3-project创建后的目录如下 (uview框架不支持vue3)运行项目使用 yarn dev:mp-weixin打开小程序开发工具引入打包后的dist包下weixin 包即可在开发工具中使用,打包发布同样HubilderX 已经支持vue3创建模板了如上图所示点击左上角新建项目后右下角有一个选择vue版本选择,默认是2 可以选择3选择版本3后创建的项目跟vue2一样但是可以使用setup语法vue3中没有this指向,所以要引入语法 如 import {onShow} from '@dcloudio/uni-app'** 下面是个demo 示例**** 控制台信息如下得知使用vite编译,意味着我们能更快启动编译以及打包,具体webpack与vite对比请自行查阅官网**[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHDfH4Bn-1660528771028)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/118e6b32b2ae4a84915d911c6c1562a5~tplv-k3u1fbpfcp-watermark.image?)]使用u-view 框架(uview框架暂时不支持vue3)1.打开终端 输入代码npm install uview-ui 具体参考官网 u-view2.引入全局uViewimport uView from 'uview-ui' Vue.use(uView) //使用rpx uni.$u.config.unit = 'rpx' 3.在uni.scss 中引用 @import 'uview-ui/theme.scss';4.在pages.json配置easycom组件模式"easycom": { // npm安装的方式不需要前面的"@/",下载安装的方式需要"@/" "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" // "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" }, 重启一下HBuilderX后该UI框架已经引入该项目中了使用vuex在uniapp中使用vuex在src目录下新建一个store文件件, 在store文件夹下创建index.js 此处我使用了vuex-persistedstate 保持数据的持久化插件 命令行: npm install --save vuex-persistedstate 然后跟vue中一样创建store在main.js中挂载 下面上代码 store/index.jsmain.js使用pinia1.安装pinia插件 npm install pinia2.创建store/user.jsimport { defineStore } from 'pinia' export const useUserStore = defineStore({ id: 'user', // id必填,且需要唯一 state: () => { return { userId: uni.getStorageSync('userId') '', } }, actions: { set(type, code) { this[type] = code; uni.setStorageSync(type, code); } }, getters: { } }) 3.在main.js中引入import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const pinia = createPinia() const app = createApp(App) app.use(pinia) app.mount('#app') 正式开发配置页面创建新的页面,并在page.json注册,并且设置状态栏等信息(具体参考uniapp官网) { "path": "pages/index/index", "style": { "navigationBarTitleText": "主页", "navigationBarBackgroundColor":"#fff", "navigationStyle":"default", "navigationBarTextStyle":"black", "enablePullDownRefresh":false, } } 配置tarbar官网tarbar"tabBar": { "list": [{ "pagePath": "pages/index/index", "text": "home" }] }, 使用ui框架uview已知uview 有两种安装方式(npm配置,### Hbuilder配置)方法大同小异此处不在展示npmp配置下载方式配置使用vant下载vant小程序源码 地址vant-weapp: 轻量、可靠的小程序 UI 组件库vant-weapp: 轻量、可靠的小程序 UI 组件库首先在src目录下创建wxcomponents/vant目录再次把下载的源码dist包"内容"放在vant目录下即可(注意不是dist文件夹而是dist包内的组件文件夹)此处是为了使用uniapp的easycom的机制,自动引入组件注意目录结构 这样就不需用使用usingCompoents了,编译后会帮我们自动注册在app.vue中添加 在page.json中添加 "easycom": { "custom": { "^van-(.*)": "@/wxcomponents/vant/$1/index" } }, 最后在需要的页面中直接使用组件即可 危险按钮最后发布小程序如果未使用的组件可以删除 减小包的体积自定义tarbart,导航栏1-1.需要在App.vue onShow中隐藏原有的tarbaruni.hideTabBar() 1-2.创建通用组件 可以使用u-view中的tarbar组件也可以自定义组件的样式,直接在页面中使用easycom是自动开启的,不需要手动开启。只需要在components目录下创建相同的名称,且是components/组件名称/组件名称.vue目录结构,就不需要在页面引用注册点我查看官网地址u-view tarbar组件具体业务逻辑,页面UI 我就不在此展示了2-1.在page.json中 设置页面style "navigationStyle": "custom",去除原生的样式2-2.在页面中引入自定义导航栏组件即可使用,如上图所示u-view navbar组件)使用自定义组件需要注意iphonex 以上机型的底部高度.box { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); } 如果改变u-view 组件样式 则需要重点注意一下两点点击查看官网详情3-1.在app和h5 中一般通过v-deep或/deep/指令修改3-2.在微信小程序中则需要在父组件的类名下更改组件类名为.navbar 微信小程序必须使用 /deep/登录流程上图为微信小程序官方登陆示意图使用uni.login由上图可得知我们需要拿uni.login()返回的code获取登陆状态打开manifest.json 打开微信小程序 设置appid且该appid需要与后端的appid一致后端与微信服务器请求得到openid与session_key,这时后端可自定义登陆状态讲一下我的登陆页面流程在home页面中判断有无登陆后存在本地的信息,如用户信息,token等,我使用的是用户手机存在本地,本地无信息的情况跳转到登陆页面,用户点击button登陆且更新用户信息,然后返回home页面加载其他信息本地已有信息,执行uni.login()更新token,防止用户token过期,最好写在onload()中,不用每次都登陆获取用户信息使用uni.getUserProfile()必须是点击事件下获取 getUserInfo获取的是匿名数据,无法使用且只能获取到用户昵称头像信息,其他获取不到每次点击都需要用户授权获取手机号码两种方法 我都使用过 目前使用的是第一种方法 第二种方法需要单独去获取uni.login的code一块传值给后端1.使用getPhoneNumber的接口 此方法必须是小程序已经认证过了地址通过getphonenumber方法返回的值 如下图所示后端可以获取code(动态令牌)实现登录并且在后端解密手机号码 具体方法如下地址2.手机号码解密方式使用button组件 open-type=“getPhoneNumber” @getPhoneNumber = getPhoneNumber 的点击事件获取加密的手机号码,在此推荐使用后端解密 可以获取uni.login 返回的code后一块传给后端既完成登陆也获取手机注册等信息 地址封装请求可以使用uview 封装好的方法,也可以自己封装多数据请求loading处理同时发起多个数据请求,判断上个请求是否结束,如果上个请求结束&& 本次请求结束 关闭loading参考其他loading封装的思路`// const baseUrl = xxxx; var num = 0; export default function requset(url, data = {}, method = "GET", getToken = true,isShowLoading = true, ) { if (isShowLoading) { num++; uni.showLoading({ title: '加载中...', mask: true }) } let token = uni.getStorageSync('token') const header = { 'content-type': 'application/json', } if (getToken) { header.Authorization = token } return new Promise((resolve, reject) => { uni.request({ url: baseUrl + url, data, method, timeout: 10000, header, success(res) { resolve(res) }, fail(err) { if (err.errMsg == 'request:fail timeout') { uni.showToast({ title: '请求超时,请重试!', duration: 2000, icon: "none" }); } else if (err.errMsg == 'request:fail ') { uni.showToast({ title: '无网络!', duration: 2000, icon: "none" }); } else { uni.showToast({ title: '服务器错误', duration: 2000, icon: "none" }); } reject(err) }, complete() { if (isShowLoading) { setTimeout(() => { num--; console.log('第几次', num) if (num === 0) { uni.hideLoading() } }, 200) } } }) }) } //在api.js中引入次方法 并且暴露出去 import requset from './requset.js' export function getLoginUserInfo() { return requset('/wx/mini/getLoginUserInfo') } 第二种使用uview的封装方法uni官方是没有路由拦截的此处使用uview的路由拦截const request = (Vue) => { Vue.prototype.$u.http.setConfig((config) => { config = configs return config }); // 请求拦截 Vue.prototype.$u.http.interceptors.request.use((config) => { uni.showLoading({ title: '加载中', mask: true, }); //设置token config.header.Authorization = uni.getStorageSync('Authorization') ''; return config }) // 响应拦截 Vue.prototype.$u.http.interceptors.response.use((response) => { if (response.data.status != 200) { uni.hideLoading(); uni.showToast({ title: response.data.msg, icon: 'none', duration: 2000 }) } return response; }, (err) => { uni.hideLoading(); // 对响应错误做点什么 if (err.errMsg == 'request:fail timeout') { uni.showToast({ title: '请求超时,请重试!', duration: 2000, icon: "none" }); } else if (err.errMsg == 'request:fail ') { uni.showToast({ title: '无网络!', duration: 2000, icon: "none" }); } else { uni.showToast({ title: '服务器错误', duration: 2000, icon: "none" }); } return err }) } export default request; 接口请求(此处用的uview已封装的api 直接拿来使用)const http = uni.$u.http; const api = '/api'; //get请求 export const getUserInfo = (params) => http.get(api + '/xxx', {params}) //post 请求 export const login = (data) => http.post('/xxx', data) 获取地址使用uni.getLocation()获取经纬度,再次之前需要判断用户是否授权,如未授权配合uni.openSeting()引导用户授权使用uni.authorize()判断用户是否授权,未授权调用uni.openSeting,授权则调用uni.getLocation uni.authorize({ scope: 'scope.userLocation', success(res) { uni.getLocation({ type: 'gcj02 ', success(res) { const latitude = res.latitude; const longitude = res.longitude; } )}, fail(err) { uni.showModal({ title: '是否开启', content: '当前需要获取您的地理位置', showCancel:false, success: function(res) { if (res.confirm) { uni.openSetting({ success(res) { if (res.authSetting['scope.userLocation']) { //执行你需要的请求操作 } else { uni.showToast({ title: '无法获取最近电站信息', duration: 2000, icon: "none" })}} }) } } }); } }) 付款流程使用微信小程序付款 uni.requestPayment() 官网地址通常微信付款所需要的数据都由后端返回,你只需要调用这个api就行// 仅作为示例,非真实参数信息。 uni.requestPayment({ provider: 'wxpay',//微信付款 appid:'', timeStamp: String(Date.now()), //时间戳 nonceStr: 'A1B2C3D4E5', //随机字符串 package: 'prepay_id=wx20180101abcdefg',//统一下单接口返回的 prepay_id 参数值 signType: 'MD5',//加密方式 paySign: '',//寄吗数据 success: function (res) { console.log('success:' + JSON.stringify(res)); }, fail: function (err) { console.log('fail:' + JSON.stringify(err)); } }); 发布hubilder 点击发行到微信小程序打包后如果提示在mainfest.json 打开源码视图 配置如下分包在pages.json中配置需要的分包subPackages 官网示例地址 配置如下图所示此时已经配置完毕,运行到微信开发者工具,点击详情,此时已经显示我们添加分包完成消息订阅目前消息订阅常用的有一次性订阅与长期订阅:长期订阅主要开发给公众单位,所以我们本次是使用一次性订阅微信官方链接uniapp官方链接[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nG6DbAab-1660528771037)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3da72aeb52e4edfb979841d6e3c413e~tplv-k3u1fbpfcp-watermark.image?)]消息订阅前我们必须在小程序后台设置消息模板点击跳转到微信公众平台登录在订阅消息一栏中设置消息模板消息模板官方提供很多种,可以自行搭配.也可以申请消息模板下面介绍如何在项目中使用订阅消息 uni.requestSubscribeMessage({ tmplIds: ['HAAIddwfUie7huMk2D3QZ-xxxxxxxxxxxxx', '2zDrf9SNpGq6hU1vSbfLQganFnQE_xxxxxxxxxxxxx' ], success(res) { console.log(res); // 值包括'accept'、'reject'、'ban'。 // 'accept'表示用户同意订阅该条id对应的模板消息, // 'reject'表示用户拒绝订阅该条id对应的模板消息, // 'ban'表示已被后台封禁 // if (res.errMsg == 'requestSubscribeMessage:ok') { // console.log('订阅成功'); // uni.showToast({ // title: '订阅成功' // }) // } }, }) 打印事件图片1. 点击事件后调用该api即可,tmplIds为消息订阅模板id,可以是多个,但是一次调用最多可订阅3条消息;2. TEMPLATE_ID 是动态的模板id,每个消息都是独立存在的,如果有需要 则要对单个进行判断;3. 如果勾选了总是保持以上选择,则不会再弹窗提示;如果只勾选了一个,并选中不在询问还会弹窗提示;4. 如果选中取消按钮,则默认一直都是拒绝状态且不会弹窗提示,此时需要uni.getSetting中引导用户打开消息提示;withSubscriptions:是否同时获取用户订阅消息的订阅状态,默认不获取(提示:在真机测试中打开消息通知之后再次点击订阅消息并没有弹窗;正确做法:在开发者工具中清楚缓存,并重新编译到真机中,此时再次点击订阅消息正常唤起弹窗事件)此时我们已经完成了一半,后面则需要消息推送 消息推送地址消息推送可以是服务端推送也可以是云函数推送消息推送一般由后端完成,我们可以把前端的参数通过接口的方式传递给服务端,服务端在合适的机会推送给给用户;注意data值要与消息模板的key值对应上;h5适配问题关于h5适配问题相信很多人都有解决方案了 分享我一下解决的办法,有更好的方法欢迎提出remrem 我没用过vwvw : 相对于视口的宽度,1vw 等于视口宽度的1%(总视口宽度为100vw)vh : 相对于视口的高度, 1vh 等于视口高度的1%(总视口高度为100vh)vmin : 选取 vw 和 vh 中最小的那个vmax : 选取 vw 和 vh 中最大的那个h5适配解决方案uniapp官网提供的解决方案rpx转px既然rpx可以转px 我们也可以用px 转vwvw 安装第三方插件postcss-px-to-viewport 正常uniapp创建的项目都已经安装了 在项目中创建一个postcss.config.js 文件 在文件中写入一下内容 viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度,iPhone 6s 一般是37 viewportHeight: 667, // 视窗的高度,iPhone 6s 一般是667 unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除) viewportUnit: "vw", // 指定需要转换成的视窗单位,建议使用vw selectorBlackList: [".ignore", ".hairlines"], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名 minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值 mediaQuery: false, // 允许在媒体查询中转换`px` fontViewportUnit: "vw", //字体使用的视口单位 exclude: [/node_modules/], vite构建的项目中使用postcss-px-to-viewport // vite.config.js import { defineConfig } from 'vite' import uni from '@dcloudio/vite-plugin-uni' // https://vitejs.dev/config/ export default defineConfig({ base: '/', plugins: [ uni(), ], css: { postcss: { plugins: [ require("postcss-px-to-viewport")({ unitToConvert: 'px',//需要转换的单位,默认为"px" viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,iPhone 6s 一般是375 viewportHeight: 667, // 视窗的高度,iPhone 6s 一般是667 unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除) viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw selectorBlackList: ['.ignore', '.hairlines'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名 minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值 mediaQuery: false, // 允许在媒体查询中转换`px` fontViewportUnit: 'vw',//字体使用的视口单位 exclude: [/node_modules/] }) ] } }, server: { host: "0.0.0.0", port: "3000", proxy: { }, }, }) 后续新增项目中使用到的功能:tailwind,关于微信小程序上线流程(2023.4.25)单点登录(url传值)顾名思义,接入第三方时候做免登陆处理,在当前页面获取到第三方页面传过来的值,多种方法(可以url传值,本地传值等)在onLoad 生命周期中拿到需要值(userId),通过调用后端接口做到免登陆处理,登录成功后返回的值存在本地或者store里面,完成免登陆处理,跳转到首页图片压缩做h5项目必定离不开图片压缩,网上也有很多插件处理, 我项目中使用canvas处理,借助uniapp的pai处理 compressionIamge(that, imageURL) { // 等比例压缩图片 可指定图片宽高 兼容性:微信小程序端正常,其他端未测试 uni.showLoading({ title: "正在压缩图片", }); let result = null; const promise = new Promise((resolve, reject) => { uni.getImageInfo({ src: imageURL, success: (res) => { let originHeight = res.height; let originWidth = res.width; let maxWidth = 480; // 最大宽 let maxHeight = 640; // 最大高 let targetWidth = originWidth; let targetHeight = originHeight; if (originWidth > maxWidth originHeight > maxHeight) { if (originWidth / originHeight > maxWidth / maxHeight) { targetWidth = maxWidth; targetHeight = Math.round( maxWidth * (originHeight / originWidth) ); } else { targetHeight = maxHeight; targetWidth = Math.round( maxHeight * (originWidth / originHeight) ); } } that.cWidth = targetWidth; that.cHeight = targetHeight; let ctx = uni.createCanvasContext("canvas", that); ctx.clearRect(0, 0, targetWidth, targetHeight); ctx.drawImage(imageURL, 0, 0, targetWidth, targetHeight); ctx.draw( false, () => { setTimeout(() => { uni.canvasToTempFilePath( { canvasId: "canvas", success: (res) => { resolve(res.tempFilePath); }, fail: (err) => { console.log(err); }, complete: () => { uni.hideLoading(); }, }, that ) }, 1000) } ); }, }); }); return promise.then((res) => (result = res)); } webViewSDK引入在index.html中引入sdk地址 判断是否处在webView的环境中 如果是webView环境在中可以注入window(也可以跳过此步骤)if(isAndroid isiOS ){ window.la=la } 然后再其他页面直接使用window.la.xxx()地图处理(腾讯地图) 注册地图获取key(自行百度) 在index.html 引入地图key 初始化地图事件 // gps/js /** * 判断经纬度是否超出中国境内 */ export function isLocationOutOfChina(latitude, longitude) { if (longitude < 72.004 longitude > 137.8347 latitude < 0.8293 latitude > 55.8271) return true; return false; } /** * 将WGS-84(国际标准)转为GCJ-02(火星坐标): */ export function transformFromWGSToGCJ(latitude, longitude) { var lat = ""; var lon = ""; var ee = 0.00669342162296594323; var a = 6378245.0; var pi = 3.14159265358979324; if (isLocationOutOfChina(latitude, longitude)) { lat = latitude; lon = longitude; } else { var adjustLat = transformLatWithXY(longitude - 105.0, latitude - 35.0); var adjustLon = transformLonWithXY(longitude - 105.0, latitude - 35.0); var radLat = latitude / 180.0 * pi; var magic = Math.sin(radLat); magic = 1 - ee * magic * magic; var sqrtMagic = Math.sqrt(magic); adjustLat = (adjustLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); adjustLon = (adjustLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); latitude = latitude; longitude = longitude; } return { latitude: latitude, longitude: longitude }; } /** * 将GCJ-02(火星坐标)转为百度坐标(DB-09): */ export function transformFromGCJToBaidu(latitude, longitude) { var pi = 3.14159265358979324 * 3000.0 / 180.0; var z = Math.sqrt(longitude * longitude + latitude * latitude) + 0.00002 * Math.sin(latitude * pi); var theta = Math.atan2(latitude, longitude) + 0.000003 * Math.cos(longitude * pi); var a_latitude = (z * Math.sin(theta) + 0.006); var a_longitude = (z * Math.cos(theta) + 0.0065); return { latitude: a_latitude, longitude: a_longitude }; } /** * 将百度坐标(DB-09)转为GCJ-02(火星坐标): */ export function transformFromBaiduToGCJ(latitude, longitude) { var xPi = 3.14159265358979323846264338327950288 * 3000.0 / 180.0; var x = longitude - 0.0065; var y = latitude - 0.006; var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * xPi); var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * xPi); var a_latitude = z * Math.sin(theta); var a_longitude = z * Math.cos(theta); return { latitude: a_latitude, longitude: a_longitude }; } /** * 将GCJ-02(火星坐标)转为WGS-84(国际标准): */ export function transformFromGCJToWGS(latitude, longitude) { var threshold = 0.00001; // The boundary var minLat = latitude - 0.5; var maxLat = latitude + 0.5; var minLng = longitude - 0.5; var maxLng = longitude + 0.5; var delta = 1; var maxIteration = 30; while (true) { var leftBottom = transformFromWGSToGCJ(minLat, minLng); var rightBottom = transformFromWGSToGCJ(minLat, maxLng); var leftUp = transformFromWGSToGCJ(maxLat, minLng); var midPoint = transformFromWGSToGCJ((minLat + maxLat) / 2, (minLng + maxLng) / 2); delta = Math.abs(midPoint.latitude - latitude) + Math.abs(midPoint.longitude - longitude); if (maxIteration-- <= 0 delta <= threshold) { return { latitude: (minLat + maxLat) / 2, longitude: (minLng + maxLng) / 2 }; } if (isContains({ latitude: latitude, longitude: longitude }, leftBottom, midPoint)) { maxLat = (minLat + maxLat) / 2; maxLng = (minLng + maxLng) / 2; } else if (isContains({ latitude: latitude, longitude: longitude }, rightBottom, midPoint)) { maxLat = (minLat + maxLat) / 2; minLng = (minLng + maxLng) / 2; } else if (isContains({ latitude: latitude, longitude: longitude }, leftUp, midPoint)) { minLat = (minLat + maxLat) / 2; maxLng = (minLng + maxLng) / 2; } else { minLat = (minLat + maxLat) / 2; minLng = (minLng + maxLng) / 2; } } } export function isContains(point, p1, p2) { return (point.latitude >= Math.min(p1.latitude, p2.latitude) && point.latitude <= Math.max(p1.latitude, p2.latitude)) && (point.longitude >= Math.min(p1.longitude, p2.longitude) && point.longitude <= Math.max(p1.longitude, p2.longitude)); } function transformLatWithXY(x, y) { var pi = 3.14159265358979324; var lat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); lat += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; lat += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; lat += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; return lat; } export function transformLonWithXY(x, y) { var pi = 3.14159265358979324; var lon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); lon += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; lon += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; lon += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0; return lon; } 自此常用的功能基本完成,其他的则是业务逻辑,后面会继续分享项目中遇到的问题,欢迎大家关注我

我要回帖

更多关于 小程序如何获取unionid 的文章

 

随机推荐