使用uni-app从0.5到1搭建App记录-2

补充上一文《使用uni-app从0.5到1搭建App记录-1》。要记录的还有很多,但是等到写的时候,发现以前碰到的问题和解决思路都快忘光了。

因为一直赶项目没有很多时间去梳理,但是梳理脉络和总结记录又是很重要的事,所以尽管再忙,也要努力把它记录完。文中如果有错误或者不严谨的地方,或者你有更好的解决思路,也请您提出宝贵的建议!

那我们进入正题吧!上一文已经把框架选型、项目搭建已经梳理完。那接下来我们将会围绕着项目完善、项目落地展开。

1.项目完善

1.1 路由配置和路由防卫

(1)我们首先从插件市场下载uni-simple-router插件。

image.png注意:
插件市场安装的用户 你需要在你项目目录下执行 npm init -y && npm install query-string -D
uni-simple-router 是专为 uni-app 打造的路由器。它与 uni-app 核心深度集成,使用 uni-app 轻松构建单页应用程序变得轻而易举。

(2)在这里只介绍重要的知识点,基本使用可以参考官网,我们接下来将从这几点开始我们的学习

配置白名单全局前置路由守卫全局后置路由守卫主页退出后登录页禁止返回路由跳转和传参

首先可以看到我们在全局引入了Router,这样我们便可以在全局很方便的使用它,还有一点的是我们需要在main.js挂载我们的全局监听,这样我们就可以在路由守卫中监听到页面的变化,而白名单是为了不需要判断token是否存在或过期你也可以跳转到该界面,就比如登录页。这也是官网中快速上手能知道的,这里不过多赘述。如下写法:涉及了白名单、路由守卫的使用。

import modules from './modules'

import Vue from 'vue'

import Router from '@/plugin/uni-simple-router/index.js'

import {ACCESS_TOKEN} from '@/common/util/constants.js'




Vue.use(Router)  // 全局注册路由
//初始化

const router = new Router({

    encodeURI: true,

    routes: [...modules]//路由表

});



const whiteList = ['/pages/login/login'];


//全局路由前置守卫
router.beforeEach((to, from, next) => {
    let token = uni.getStorageSync(ACCESS_TOKEN);
     if (token) {
        next()
     } else {
         if (whiteList.indexOf(to.path) !== -1) {
            next()
         } else {
             next({path: '/pages/login/login'})
         }
     }
})
// 全局路由后置守卫
router.afterEach((to, from) => {
    console.log(to, from)
})
export default router;

但是上面写法我碰到了一个很严重的问题,在手机App中,我登录退出后跳转到登录页,然后关闭该应用后,我再次打开,我点返回页,它会返回到主页。这是我不想看到的,所以我尝试着在登录页这样写,但是效果不理想。

onBackPress(options) {  //返回时会调到的
	   this.backButtonPress++;   //别忘了定义一下
  if (this.backButtonPress > 1) { 
    plus.runtime.quit();
    return false;  //允许返回
  } else {
    plus.nativeUI.toast('再按一次退出应用');
    return true;  //阻止返回
  } 
  setTimeout(function() {
    this.backButtonPress = 0;
  }, 1000);
}, 

所以我最终还是在路由守卫中解决了这个问题。如下,我在没有登录的前提下(也就是没有token的情况下),用repalceAll来关闭所有历史记录,如果点击返回跳转的不是登录页,那么我就替换到登录页。如下代码:

import modules from './modules'

import Vue from 'vue'

import Router from '@/plugin/uni-simple-router/index.js'

import {ACCESS_TOKEN} from '@/common/util/constants.js'




Vue.use(Router) // 全局注册路由
//初始化

const router = new Router({

    encodeURI: true,

    routes: [...modules]//路由表

});



//全局路由前置守卫
router.beforeEach((to, from, next) => {
    let token = uni.getStorageSync(ACCESS_TOKEN);
    if (!token&&to.path !== '/pages/login/login') {
          return next({
             path:'/pages/login/login',
             NAVTYPE:'replaceAll'
          });
     
    }else{
       next()
    }
})
// 全局路由后置守卫
router.afterEach((to, from) => {
    console.log(to, from)
})

export default router;

接下来我们在简单介绍几种路由跳转的方式,分别在以下场景:页面跳转,页面返回,页面登出,页面传参取值我们都需要使用哪些api:

  1. 页面跳转:
this.$Router.push({name:'我们的name'})
// 其中name对应routes中的路由表
const router = new Router({
   encodeURI: true,
   routes: [...modules]//路由表
});
// 路由表示例:
{
path: "/pages/login/login",
name: 'login',
   meta: {
      title: '登录',
   },
},

2.页面返回

// 上一个,等同 uni.navigateBack
this.$Router.back(); 
// 后退 2 步
this.$Router.back(2)
// 

3.页面登出

this.$Router.replaceAll({name: 'index'})

4.路由传参和取值

// 传参
this.$Router.push({name: 'index',params:{
   a:1
}})
// 取值
let params=this.$Route.query
console.log(params.a)
1.2 request请求封装和api地址动态配置

1.首先我们还是从uniapp插件库中引用luch-request,我使用的是V2.0.1

luch-request 是一个基于Promise 开发的uni-app跨平台、项目级别的请求库,它有更小的体积,易用的api,方便简单的自定义能力。
image.png
2.新建公共service目录和api目录。
service目录用来存储公共request封装,api目录用来写api请求的。如下:

image.png

3.使用luch-request

  • config.service.js文件代码

通过config.service.js配置不同环境的地址,或者地址公共后缀。

let BASE_URL = ''
if (process.env.NODE_ENV == 'development') {
    BASE_URL = 'http://127.0.0.1:8080/tunnel'; // 开发环境
} else {
    BASE_URL = 'http://127.0.0.1:8080/tunnel'; // 生产环境
}

// let routeUrl = BASE_URL;
let routeUrl = BASE_URL + '/route';
const configService = {
    apiUrl: routeUrl,
    loginUrl: BASE_URL,
};


export default configService
  • service.js文件代码

service.js涉及了token校验判断是否登录过期时间超时配置请求拦截器中动态修改ip端口响应拦截器中判断状态码退出到登录页

import Request from '@/common/luch-request/index.js'
import {ACCESS_TOKEN} from '@/common/util/constants.js'
import configService from './config.service.js'
import tip from '@/common/util/tip.js';



// let preUrl = configService.preUrl;
let apiUrl = configService.apiUrl;

const getTokenStorage = () => {
    let token = ''
    try {
        token = uni.getStorageSync(ACCESS_TOKEN)
    } catch (e) {
        //TODO handle the exception
        console.log("getTokenStorage", token)
    }
    return token
}



const http = new Request()
http.setConfig((config) => { /* 设置全局配置 */
    config.baseUrl = apiUrl /* 根域名不同 */
    config.header = {
        ...config.header,
    }
    config.timeout=15000
    return config
})


/**
 * 自定义验证器,如果返回true 则进入响应拦截器的响应成功函数(resolve),否则进入响应拦截器的响应错误函数(reject)
 * @param { Number } statusCode - 请求响应体statusCode(只读)
 * @return { Boolean } 如果为true,则 resolve, 否则 reject
 */
// 有默认,非必写
http.validateStatus = (statusCode) => {
    return statusCode === 200
}

http.interceptor.request((config, cancel) => { /* 请求之前拦截器 */
    config.header = {
        ...config.header,
        'X-Access-Token': `sessionId=${getTokenStorage()}`,
        "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
    }
    // 用来动态修改ip和端口的
    let ip = uni.getStorageSync('ip');
    let port = uni.getStorageSync('port');
    if (!!ip && !!port) {
        config.baseUrl = 'https://' + ip + ':' + port;
    }

    /*
    if (!token) { // 如果token不存在,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行
      cancel('token 不存在') // 接收一个参数,会传给catch((err) => {}) err.errMsg === 'token 不存在'
    }
    */
    return config
})

// 必须使用异步函数,注意
http.interceptor.response(async (response) => { /* 请求之后拦截器 */
    if (response.statusCode !== 200) { // 服务端返回的状态码不等于200,则reject()
        this.$tip.info(response?.data.msg || response.data.message || '未知异常,请联系管理员!')
        return Promise.reject(response)
    }else{
        let data = response.data;
        if (!data.success) {
            // 失败校验
            let timeout = setTimeout(
                uni.showToast({
                    title: data.msg || data.message || '未知异常,请联系管理员!',
                    icon: 'none'
                })
                , 1000);
            clearTimeout(timeout);
        }
    }
    return response
}, (response) => {
    // 请求错误做点什么
    console.log("请求错误做点什么", response);
    if (response) {
        let {data,statusCode} = response;
        const token = uni.getStorageSync(ACCESS_TOKEN);
        if (statusCode === 401) {
            let timeout = setTimeout(tip.alert('登录已过期'), 1000);
            uni.removeStorageSync(ACCESS_TOKEN);
            uni.redirectTo({
                url: '/pages/login/login'
            });
            clearTimeout(timeout);
            return;
        }

    }
    return response
})

export {
    http
}
  • api目录service示例
import { http } from '@/common/service/service.js'
import configService from '@/common/service/config.service.js';
const apiUrl = configService.loginUrl;
const apiService = {
   /**
     * 退出
     */
   logout(params) {
      return http.get(apiUrl+'/loggingout',params);
   },

   /**
    * 修改密码
    */
   updatePwd(params) {
      return http.post('/sysUser/updatePwd',params);
   },
};


export default apiService;
  • 界面请求api修改密码示例
confirmPwd() {
  this.showPwdModal = true;
  const {userPwd, newPwd, pwd_check} = this.passwordModel;
  const pwdParams = {
    userPwd: md5(userPwd),
    newPwd: md5(newPwd),
    pwd_check: md5(pwd_check),
  }
  if (this.passwordModel.newPwd != this.passwordModel.pwd_check) {
    this.$tip.toast('新密码和确认密码不一致');
    return;
  }
  this.$refs.uForm.validate().then(res => {
    api.updatePwd({...pwdParams}).then(res => {
      if (res?.data?.success) {
        this.showPwdModal = false;
        this.$tip.success('修改成功!');
        uni.removeStorageSync(ACCESS_TOKEN);
        uni.removeStorageSync(USER_NAME);
        uni.removeStorageSync(USER_INFO);
        this.$Router.replaceAll({name: 'login'})
      } else {
        console.log(res)
        this.$tip.toast(res?.data?.data?.msg||'修改失败!');
      }
    })
  }).catch(errors => {
    this.$tip.error('校验失败')
  })
},
  • 方法技巧:如果你觉得每个界面都引入api目录很麻烦,你可以这样做:

你每个模块一个服务模块文件,然后通过index.js文件在main.js进行挂载就可以了。
image.png

// index.js文件
import electromechanicalApi from "./electromechanicalApi";
import inspectionTasks from "./inspectionApi";
import warningApi from "./warningApi";
import tunnelApi from "./tunnelApi";
import realTimeSensorApi from "./realTimeSensorApi";
import api from "./api";
export {electromechanicalApi,api,inspectionTasks,warningApi,tunnelApi,realTimeSensorApi};
// main.js
import apiRequest from './api/index';
Vue.prototype.$apiRequest=apiRequest;

然后你就可以在任意界面访问模块服务了,比如我们上面的修改密码可以写成这样

    this.$apiRequest.api.updatePwd({...pwdParams}).then(res => {
      ...
    })

同理很多公用组件和公用方法都可以这样做!

2.阶段疑难汇总

2.1app横屏锁定
  1. 配置manifest.json文件:
"app-plus" : {
     "screenOrientation" : [
            //可选,字符串数组类型,应用支持的横竖屏
            "portrait-primary" //可选,字符串类型,支持竖屏
        ]
}
  1. 在App.vue文件onLaunch生命周期函数新增
// #ifdef APP-PLUS
plus.screen.lockOrientation("portrait-primary") // 竖屏锁定
// #endif
  1. 注意

这个属性已经废弃,一开始我就是使用这个没生效搞了半天!!!在这里给大家避个坑
image.png
应该使用这个:
image.png

  1. 配置链接

manifest.json文件配置直达

2.2横屏竖屏切换,样式错乱问题

这个官方好像没有具体的解决办法,所以只好自己尝试。我这里先说我的解决方案以及场景,毕竟每个人的像素使用方案不一致。

  1. 场景:除了横屏都是使用upx写的界面,然后横屏使用的方案是vw,vh,px

  2. 问题描述:因为项目属于内部项目,所以只能画图描述自己的问题,如下图

image.png
3.解决方案:

(1)在横屏B界面加入如下代码。

onLoad() {
  // #ifdef APP-PLUS
  uni.showLoading({
    title: "加载中..."
  })
  this.timer1=setTimeout(() => {
    let orientation = plus.navigator.getOrientation();
    if (orientation == 0) {
      plus.screen.unlockOrientation(); //解锁横屏竖屏方向
      plus.screen.lockOrientation('landscape-primary'); // 锁定横屏
      plus.navigator.setFullscreen(true); // 设置全屏
      // plus.navigator.hideSystemNavigation(); // 隐藏系统导航栏
    }
    uni.hideLoading();
  }, 800)
  // #endif
},
onUnload() {
  // #ifdef APP-PLUS
  plus.screen.unlockOrientation();
  plus.screen.lockOrientation('portrait-primary');
  plus.navigator.setFullscreen(false);
  // plus.navigator.showSystemNavigation();
  // #endif
  if(this.timer1){
    clearInterval(this.timer1);
    this.timer1=null
  }
},

到这里我当时以为是解决了,后面发现是白高兴一场,只是部分手机可行,而一些配置较低的手机还是会出现同样的问题,所以我加了下面的步骤。

(2)在page.json文件加入:

 "globalStyle": {
      "rpxCalcMaxDeviceWidth":0
    }

到这里完美解决了,但是我发现我要隐藏掉系统导航栏,旧手机进入这个界面会出现高度变得非常小的问题,暂时还没解决方案,后续有解决办法继续补充!!

4.官方讨论

官方问题汇总答案直达

后续请看下文…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYxguskG' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片