Fork me on GitHub

热点项目移动端总结

热点移动端总结

2021.5.14

  • 今天完成了频道编辑模块和文章搜索模块中的搜索历史和搜索结果、联想搜索。

  • 至今为止遇到的技术难点(知识掌握不牢固):

    • vuex 刷新过后如何保持状态不丢失?

    • 如何做数据防抖处理(时间转换及深拷贝等数组去重的第三方方法运用)

    • van-tabs 每个选项卡所展示的数据不同,怎么实现的思路?

    • 如何使 van-ceil 展示的指定数据高亮显示

    • 添加数据到数组中并且数组去重并保证加入的数据在数组的最前面业务逻辑(简单实现)?怎么运用 new Set 进行去重?
    • 如何做好移动端的适配?
    • 频道编辑模块中,获取我的频道和频道推荐数据,之后再对我的频道进行添加或者删除,在对频道推荐进行列表渲染时普通直接对数组进行渲染,后期需要单独对数组设置添加或者删除,有更好的解决方式?
  • 目前解决方案

    • 过 vuex 管理状态和本地存储技术 localstorage 来解决,当 vuex 状态初始化的时候就从本地存储中获取数据

      • import Vue from "vue";
        import Vuex from "vuex";
        import { setItem, getItem } from "@/utils/storage";
        Vue.use(Vuex);
        const USER = "redian-user";
        export default new Vuex.Store({
        state: {
          user: getItem(USER),
          // user: JSON.parse(window.localStorage.getItem("user"))
         },
         mutations: {
          setUser(state, data) {
           state.user = data;
           // window.localStorage.setItem("user",JSON.stringify(state.user))
           setItem(USER, state.user);
          },
         },
         actions: {},
         modules: {},
        });
        
    • 本地存储模块

      • // 获取状态值
        export const getItem = name => {
        	const data = window.localStorage.getItem(name)
        	//data可能不是JSON字符串
             try {
              	return JSON.parse(data)
             }catch (error) {
              	return data
             }
        }
          // 改变状态值
         export const setItem = (name,value) => {
             if(typeof(value)==='object') {
              	value = JSON.stringify(value)
             }
             window.localStorage.setItem(name,value)
            }
         //删除状态值
         export const removeItem = name => {
            window.localStorage.removeItem(name)
         }
        
    • 通过 lodash 里的 debounce 进行数据防抖及 dayjs 进行日期处理

      • watch: {
        
              searchText: {
        
               handler: debounce(async function() {
        
            ​    const { data: res } = await getSearchSuggestion(this.searchText);
        
            ​    this.suggestion = res.data.options;
        
               }, 200),
        
               immediate: true,
        
              },
        
             },
        
      • var dayjs = require("dayjs");
          
        import Vue from "vue";
          
        //import dayjs from 'dayjs' // ES 2015
          
        // 加载中文语言包
          
        import "dayjs/locale/zh-cn";
        

        import relativeTime from "dayjs/plugin/relativeTime";

        // 配置使用处理相对时间的插件

        dayjs.extend(relativeTime);

        // 配置使用中文语言包

        dayjs.locale("zh-cn");

        // 全局过滤器:处理相对时间

        Vue.filter("relativeTime", (value) => {

        return dayjs().from(dayjs(value));

        });

        
        
    • 通过封装组件的方式,把数据通过 props 来传给子组件进行传递,通过正则匹配和 v-html 指令来实现。

      • highlight(item) {
        
           //RegExp 是正则表达式的构造函数
        
           //参数1:字符串
        
           //参数2: 匹配模式
        
           //返回值:正则对象
        
           return item.replace(
        
        ​    new RegExp(this.searchText, "gi"),
        
        ​    `<span style="color:red">${this.searchText}</span>`
        
           );
        
         },
        
    • 通过数组的 indexof 方法来实现

      • //数组去重
        
           const index = this.searchHistory.indexOf(searchText);
        
           if (index !== -1) {
        
        ​    this.searchHistory.splice(index, 1);
        
           }
        
           this.searchHistory.unshift(searchText);
        
        new set去重方式(两个数组合并)
        
        //加载本地和线上历史记录
        
          async loadSearchHistory() {
        
           let localSearchHistory = getItem("search-history") || [];
        
           if (this.user) {
        
        ​    const { data: res } = await getSearchHistory();
        
        ​    //数组去重
        
        ​    localSearchHistory = [
        
        ​     ...new Set([...localSearchHistory, ...res.data.keywords]),
        
        ​    ];
        
           }
        
           this.searchHistory = localSearchHistory;
        
          },
        
    • 目前移动端适配是通过 postcss 来进行适配,具体配置在 postcss.config.js。

      • Autoprefixer 插件可以实现自动添加浏览器相关的声明前缀

      • PostCSS Preset Env 插件可以让你使用更新的 CSS 语法特性并实现向下兼容

      • postcss-pxtorem 可以实现将 px 转换为 rem

      • Vue CLI 默认集成了 PostCSS,并且默认开启了 autoprefixer 插件。

      • postcss-pxtorem的配置文件

        • // postcss.config.js
          
          module.exports = {
          
           plugins: {
          
            //兼容的版本
          
            // autoprefixer: {
          
            //  browsers: ["Android >= 4.0", "iOS >= 8"],
          
            // },
          
            "postcss-pxtorem": {
          
             rootValue: 37.5,
          
             propList: ["*"],
          
            },
          
           },
          
          };
          
    • 通过 computed 属性,运用数组的 filter 和 find 方法来处理数组,然而 computed 属性会自动随着属性值改变而改变

      • computed: {
        
          ...mapState(["user"]),
        
          recommendChannel() {
        
           return this.allChannels.filter((allchannel) => {
        
        ​    return !this.channels.find((channel) => {
        
        ​     return allchannel.id === channel.id;
        
        ​    });
        
           });
        
          },
        
         },
        

2021.5.18

  • 今天做了文章详情和发布评论以及评论列表模块,目前遇到的技术难点有:

    • 去文章详情页面的传递的路由参数整数过大,导致传递过去的路由参数拿不完整
    • 每次数据发生变化都需要本地存储,一个一个存储代码糅合。
  • 目前解决方案

    • 运用 JSONBIG 解决参数过大的问题,让大整数转换成对象,需要使用的时候转换一下即可。在 axios 请求模块中加入以下代码。

      • npm i json-bigint

      • import jsonbig from "json-bigint"
        
        //封装请求模块
        
        const request = axios.create({
        
         baseURL: "http://ttapi.research.itcast.cn/", // 基础路径
        
         // transformResponse 允许自定义原始的响应数据(字符串)
        
         transformResponse: [
        
          function(data) {
        
           try {
        
        ​    // 如果转换成功则返回转换的数据结果
        
        ​    return jsonBig.parse(data);
        
           } catch (err) {
        
        ​    // 如果转换失败,则包装为统一数据格式并返回
        
        ​    return {
        
        ​     data,
        
        ​    };
        
           }
        
          },
        
         ],
        
        });
        
    • 通过 watch 函数来监视数据的变化然后对数据进行统一存储。

      • watch: {
        
          //通过监视对历史记录数据进行统一处理
        
          searchHistory() {
        
           setItem("search-history",this.searchHistory)
        
          }
        
        },
        

2021.5.21

  • 最后几天项目终于完工了,最终完成了编辑资料以及小智同学、功能优化。

  • 遇到的困难:

    • 编辑资料基本都是 vantUI 组件的应用,但是其中照片更新以及图片裁切怎么做?
    • 小智同学是通过 websoket 协议进行通信的,怎么拿到返回的数据和把自己的数据进行渲染?
    • token 过期如何解决?
    • 组件缓存带来影响怎么解决?比如文章详情返回上一步就会回到文章顶部。
    • 处理页面的访问权限?
    • 如何对移动端网站进行应用加壳?
  • 目前解决方案

    • 照片更新是通过 file 类型的 Input 框进行文件上传,通过@onchange 事件

      • <input type="file" hidden ref="file" accept="image/*" @change="onChange" />
        
      • 然后拿到input框的dom对象,通过this.$refs.file.files[0]拿到这个文件类型的图片,把图片传给图片的弹出层,通过cropperjs进行图片裁切

      • import "cropperjs/dist/cropper.css";
        import Cropper from "cropperjs";
        
      • 在mounted函数中

      •     const image = this.$refs.image;
            let cropper = new Cropper(image, {
              viewMode: 1,
              dragMode: "move",
              aspectRatio: 1,
              autoCropArea: 1,
              cropBoxMovable: false,
              cropBoxResizable: false,
              background: false,
              movable: true,
            });
            this.cropper = cropper;
        
      • 然后裁切对象通过getCroppedCanvas方法实现

      • getCroppedCanvas() {
              return new Promise((resolve) => {
                this.cropper.getCroppedCanvas().toBlob((blob) => {
                  resolve(blob);
                });
              });
            },
        
      • 最后接口需要一个 FormData

      • const file = await this.getCroppedCanvas();
             const fd = new FormData();
             fd.append("photo", file);
             //传递的是个form-data
             await editUserPhoto(fd);
             this.$emit("update-photo", window.URL.createObjectURL(file));
             this.$toast.success("保存成功");
        
    • 通过 socketio 来进行 websocket 连接

      • 在 created 函数中

      • const socket = io("http://ttapi.research.itcast.cn");
        
        
        // 当客户端与服务器建立连接成功,触发 connect 事件
        
        socket.on("connect", () => {
          console.log("建立连接成功!");
        });
        
        
        / 监听接收服务端消息
        
        socket.on('message', data => {
        
        console.log('收到服务器消息:', data)
        
        })
        
      • 发送消息的事件如下

      • onSendMessage () {
        
        const message = this.message.trim()
        
        if (!message) {
        
        return
        
        }
        +
        
        // 发送消息
        
        this.socket.emit('message', {
        
        msg: message,
        
        timestamp: Date.now()
        
        	})
        }
        
      • 最后把消息存储在数组中并清空文本框

      • this.messages.push({
          
        message,
        
        })
        // 清空文本框
            this.message =
        
      • 让消息始终滚动在最底部

      • const messageList = this.$refs['message-list']
        messageList.scrollTop = messageList.scrollHeight
        
    • token 过期

      • 通过 axios 的响应拦截器进行 token 过期处理。通过响应拦截,对响应错误根据返回的不同的响应进行不同的响应提示。其中 token 过期是 401 响应码,先判断 vuex 中的 user 有没有,没有就跳转到登录页面进行登录,并封装一个方法,让跳转过去并携带当前页面的路由

      • /跳转到登录页
        function redirectLogin() {
          router.push("/login?redirect=" + router.currentRoute.fullPath);
        }
        
      • 其次再拿到 user 的 refresh_token 再一次进行请求,然后拿到新的 token 赋给 vuex 的 token,通过 vuex 的 setUser 方法把 user 重新赋回去,最后把错误对象进行新的请求

      • try {
                const { data: res } = await refreshTokenReq({
                  url: "/app/v1_0/authorizations",
                  method: "PUT",
                  headers: {
                    Authorization: `Bearer ${user.refresh_token}`,
                  },
                });
        
        ​    //拿到新的token再给赋回去
        ​    user.token = res.data.token;
        ​    store.commit("setUser", user);
        ​    //error.config是本次请求的相关配置对象
        ​    //通过新的user对象进行之前失败的请求
        ​    return request(error.config);
          } catch (error) {
        ​    redirectLogin();
          }
        
      • 登录成功后返回之前页面

      • const redirect = this.$route.query.redirect || "/";
        this.$router.push(redirect);
        
    • 组件缓存。通过 include 属性对想要缓存的组件进行缓存,但也会带来一定的问题,在 vuex 中定义 cachePage 状态,属性值是一个数组,然后映射在 app.vue 中绑定给 include 属性中。

      • 在 layout 组件中去添加缓存

      • mounted() {
          this.$store.commit("addCache", "layoutIndex");
        },
        
      • 在 login 组件中登录成功后移除缓存

      • //清除 Layout 缓存,让他重新渲染
        this.$store.commit("removeCache", "layoutIndex");
        
    • 处理页面的访问权限,通过给路由添加一个 meta 属性

      • meta: { requiresAuth: true }
        true代表需要拦截
        然后通过导航守卫进行路由拦截
        router.beforeEach((to, from, next) => {
          if (to.meta.requiresAuth) {
            if (store.state.user) {
              return next();
            }
            Dialog.confirm({
              title: "访问提示",
              message: "该功能需要登录才能访问,确认登录吗?",
            })
              .then(() => {
                // on confirm
                router.push("/login?redirect=" + router.currentRoute.fullPath);
              })
              .catch(() => {
                // on cancel
                next(false);
              });
          } else {
            next();
          }
        });
        
    • 加壳技术

      • 通过 Dcloud 的 HbuilderX 的 5+runtime 来进行加壳
        • 将 vue 进行打包,真机调试的话保证是在同一个局域网。
        • 通过设置 manifest.json 进行配置
        • 最后通过 HbuilderX 导入 Public 文件然后运行在 android 即可。
      • 通过 Dcloud 的 HbuilderX 的 5+runtime 来进行加壳
        • 将 vue 进行打包,真机调试的话保证是在同一个局域网。
        • 通过设置 manifest.json 进行配置
        • 最后通过 HbuilderX 导入 Public 文件然后运行在 android 即可。
posted @ 2021-05-14 09:46  雨梦Coder  阅读(80)  评论(0)    收藏  举报