# 实习心得

# 实习经历

前期跟随导师一起完成需求,后期独立负责交易中心一个业务模块

  1. 根据需求添加或更改交易中心后台管理系统部分页面样式,提升了对浏览器兼容性方面的技能
  2. 使用dva配合redux跟随导师一起完成订单接入支付单项目
  3. 独立完成支付组件的迭代,并使用hooks和策略模式对支付组件进行升级封装
  4. 熟悉antd组件库开发复杂表格和表单开发
  5. 独立完成支付记录详情通用组件的开发,并上传至内网npm

淘系 (opens new window) 滴滴 (opens new window)

# 成长

# 人际交往

说实话,应届生多少都还带一些书生气,尤其是本科生。如何尽快地融入一个大集体,融入社会,是我最先面临的问题。

大学入学,大家会通过军训的方式相互了解认识,帮助我们尽快融入集体。

到了公司,大家都有自己的工作,每天都是忙碌充实的,如何相互了解认识,去建立良好的人际关系是首当其冲需要解决的问题,也是最重要的问题。

于我而言,我个人的做法就是珍惜每一次表达自我的机会,无论是周会还是团建活动,有表达自我的机会就好好把握在手。

# 技术能力

前期:熟悉环境+总结调试方法

首先是基本上入门react生态,包括政采云前端项目主要的框架dva等,这中间还夹杂这一些客户端框架,比如说electron,不过也只是初步上手

前期其实大部分时间是在熟悉环境,熟悉项目,熟悉我和导师负责的业务线逻辑

然后慢慢地,从前期的小需求的开发,到后面和导师一起完成需求拆分和分工,完成一次一次需求成功上线,独立完成一个业务组件的编写,并上传私服npm。

前期在写项目的过程中,由于不熟悉项目,经常在调试页面的时候找不到组件代码。在这里要感谢@九渊,全程手把手教导我,教我怎么使用vscode调试项目,并且准确定位代码位置。

后面经过一段时间的学习,我也总结出了解决开发过程中bug的思路

首先也是最重要的,检查自己的代码逻辑 ===> Debug定位问题  ===> 查看文档能否解决  ===> 不能解决的话定位到框架或者引用的类库  ===> 首先考虑引用是否出错,然后阅读文档检查兼容性 ===> 以上问题还是无法解决,就去和框架/组件库相关同学交流

这个流程我一直到现在都遵守,大大的减少了处理问题时浪费的时间

中后期:利用所学开发需求和维护组件

我发现写需求不仅可以帮助快速入手项目,并且可以磨练对业务的理解能力。不管所处的角色是什么,理解业务都是一步至关重要的步骤。

下面主要介绍下支付记录详情组件开发中遇到的一些难题

Situation

记得在写一个支付组件的时候碰到点问题,需求记得不是很清,反正我这边的需求是通过判断当前的支付情况和当前用户身份去控制当前订单的待支付步骤条的气泡文案。

task :

看了一下交互,发现文案通过三个状态控制,一个是当前用户是否是经办人,一个是当前订单的支付状态,一个是当前用户是否是审核

心里一想这不是挺简单的吗,不就是通过几个条件去控制文本吗,然后写着写着第一套代码就写了出来。写着写着,发现我这个函数中存在了大量的条件判断,大量的if-else,总共有三十多种情况吧,反正代码堆得跟屎山一样

讲道理这代码我看不下去了,于是乎我用switch-case改了一下,返现写不下去了,因为动态的状态有三个,咋写呢

然后在掘金上看到了一篇策略模式文章,里面详细的讲解了一下如何优化多层嵌套条件代码,于是乎我就照猫画虎的改造了一下,首先我定义了一个map,每个元素的key是一个对象,对象里面就是三种动态变化的状态的取值,然后value是一个文本的映射。最后执行函数大概是这样的,首先将整个map解构出来,也是一个数组,然后找到满足条件的元素,然后调用call方法这行整个元素的value。但是还是发现整个Map比较臃肿,代码还是很多。

然后想着想着,灵机一动,我把这三个状态通模板字符串拼在一起,不就成了一个状态了吗。然后只需要定义一个对象,对象的key是这么多情况的 三个状态值的字符串拼接而成,然后对应的value直接取对应的文本映射,然后大功告成,只需要维护这个对象即可

result

于是乎好像领略到了优化多条件嵌套语句的终极方法,总结如下,并不是所有的情况都能套用策略模式模板去优化的,可以采用映射的角度去优化问题,代码从一百行优化到20行不到,很是欣慰

后期也是做需求做需求,然后后面有同学离职,一部分业务交给我单独负责,那段时间压力也挺大的,一方面要做自己和mentor这边的需求,另一方面要先理解熟悉新业务的逻辑,然后去熟悉代码,然后快速进入开发阶段,一开始是有点懵的,但是后面花了两个下午我找了后端同学一起对下逻辑,后端同学也是很配合,就差不多上手了

# 项目总结

# 悦读ECUT

Star法则

那我介绍一下悦读ECUT这个项目吧,这个项目是我和一位后端同学在19年12月合作完成的一个项目,这个项目集合了书城,阅读器,音乐版块为一体的微信小程序,前端主要的技术栈是mpvue,然后这个项目也是设计之初也是作为参赛项目的。

接下来我讲一下该项目我认为的几个难点吧, 首先第一个难点就是微信小程序原生的wx.request并不支持promise,在异常拦截方面比较难处理,然后因为平时写多了Vue的项目,对reuqest库的使用也不太习惯,然后我就去查找了一下如何将原生wx.request改造成支持promise的方法。然后进行分析对比找到一个类库,FLYIO,不仅可以兼容多种跨段框架,并且支持promise。然后我就在项目中引入了这个库,并且结合promise进行封装,进而支持异常拦截。

然后第二个难点是当时在开发书城首页的版块卡住了,因为设计图里面有三种版块,一种免费阅读一种本周热读还有一种你最近阅读,但是这三个版块的布局截然不同,一种是一行展示四本图书,书名展示在图书盒下方,一种是两行,每行展示两本图书,然后书名和作者展示在图书盒的右侧,还有一种是三行两列。当时我是这样处理的,我单独将这几种模块抽成了一个组件。组件需要传入的参数有行和列,当前组件展示模式(mode=col,多列展示,mode=row 多行展示),然后就是数据。在组建内部首先将传入的图书数据根据传入的行和列,转为对应的多为数组,以方便展示。比如免费阅读版块是一行四列展示,我就传入row=1,col=4,mode=row,组件内部写一段逻辑将数据转为一行四列的数组之后,然后就是利用flex布局判断mode是col还是row进而进行一系列的布局。

接着第三部分也就是最重要的部分,阅读器开发,阅读器部分是利用epujs开发的web页面,也就是通过web-view嵌入小程序实现阅读书籍效果,这里开发了目录换肤,进度控制还有切换字体。我主要讲下换肤这一块吧,换肤这一部分也是开发的时间稍微长的一部分。整体来说思路:初始化阅读器注册主题文件(css文件注册到epub的theme实例) ==> 编写唤起面板选择主题事件 ==> 动态添加css文件到头部,每次切换主题都需要删除之前插入的文件

首先是初始化实例时注册主题,这里我使用到了Vuex来记录当前主题,并且每次切换主题都会保存到缓存当中,初始化时先读取是否缓存中有,然后再进行注册并且保存到Vuex

动态添加css文件到头部是这样实现的,我编写了两个方法,一个是在头部添加css文件方法,一个是删除之前添加的css文件方法,在切换主题事件最前面先调用删除头部css文件方法,然后通过策略模式根据当前点击的主题更新Vuex的主题,然后调用头部添加css文件方法,更新css文件。这两个方法主要是些dom操作,都是去操作link这个标签

然后这个项目的主要成果:首先参加过两个比赛,一个是中国计算机设计大赛,拿了国家二等奖 还有一个是江西省计算机作品大赛,拿了三等奖,另外对于我本人,这个项目因为是合作开发,也是我第一次独立完成前后端对接分离的项目,极大的促进了我的自信心,然后对我的不管是编码能力,编写css,思考问题能力,解决兼容性问题都有了极大的帮助。

利用本地存储和 epub 库提供的 cfi 分页算法以及 locations 对象完成阅读器历史进度读取

想要完成读取上一次历史进度,其实就是要获取到当前页所处的位置,然后调用epubjs的display,将其渲染出来,但是怎么才能获取到位置信息呢,经过查阅一番epubjs的文档之后,书籍实例方法有一个locations属性,要获取到当前页的位置。首先需要使用locations上的generate方法将整本书按照指定屏幕宽度和字体大写标准分页。

拿到分页之后的书籍实例之后,实例的rendition方法上有一个属性叫做currentLocation对象,这个就是当前的位置对象,这个位置对象也是通过简易的cfi分页算法获取到的。接着就需要将其和书籍名称对应,保存到本地存储,然后其实进度条也是通过这个locations对象,通过cfi算法,传入当前的cfi获取到当前的进度百分比,接着去保存到本地。

拿到当前的location和进度百分比之后,我们每次在初始化加载阅读器实例时都去缓存中获取这个currentLocation,然后调用display方法去渲染就行了

# 使用flyio改造wx.request请求库

Situation:

由于原生 wx.request 是基于 request 库进行开发的,并不支持 promise,所以在调用这个API的时候会比较麻烦,这里借助到一个请求库并将它改根据自己的需求封装自己的请求方法,方便进行请求异常处理。

Task :

  1. 首先需要判断场景调用 fly 库,微信平台才调用
  2. 基于 Promise 改造 get,post请求,处理异常

Action:

  1. 创建fly实例,需要利用 mpvuePlatform 判断是否是 wx平台
  2. 使用fly请求数据,返回一个Promise对象,对响应数据做处理
function createFly () {
  // 判断平台
  if (mpvuePlatform === 'wx') {
    const Fly = require('flyio/dist/npm/wx')
    return new Fly()
  } else {
    return null
  }
}
function hanldeError (err) {
  console.log(err)
}
// 封装get请求
export function get (url, params = {}) {
  const fly = createFly()
  if (fly) {
    return new Promise((resolve, reject) => {
      fly.get(url, params).then(response => {
        if (response && response.data) {
          resolve(response)
        } else {
          const msg = (response && response.data && response.data.msg) || '请求失败'
          mpvue.showToast({
            title: msg,
            duration: 2000
          })
          reject(response)
        }
      }).catch(err => {
        hanldeError(err)
        reject(err)
      })
    })
  }
}

# 引入lin-ui和vant组件库部分组件配合完成页面开发

# 首页图书展示动态多栏布局

Situation:

因为首页布局会遇到很多种不同分栏情况,比如一行分四栏,或者两行两列

Task:

编写一个通用组件,传入行和列,根据传入的行和列将数据改造成对应的多维数组,多维数组的元素个数就是行,每个元素数组的元素个数就是列。然后使用 flex 布局进行布局。左右布局和上下布局可以通过传入一个 mode控制 ,然后使用 v-if 配合 flex布局进行控制

Action:

  1. 根据行列数,改造bookData,改造成对应的多维数组
    bookData () {
      // 将数组数据结构转换成想要的数据结构(传入行列)
      if (this.data && this.data.length > 0) {
        this.data.forEach((item) => {
          // 增加中文分类名属性
          item.text = CATEGORY[item.categoryText.toLowerCase()]
        })
        const number = this.row * this.col
        const _bookdata = this.data.slice(0, number)
        const _BookDataByRow = []
        let _row = 0
        while (_row < this.row) {
          _BookDataByRow.push(
            _bookdata.slice(_row * this.col, _row * this.col + this.col)
          )
          _row++
        }
        return _BookDataByRow
      } else {
        return []
      }
    }
  1. 利用flex布局,实现多列多栏布局
   <div class="home-book-content">
    <!--行-->
      <div class="home-book-row"
           v-for="(item,index) in bookData"
           :key="index">
    <!--列-->
        <div class="home-book-col"
             :style="{flex :'0 0 '+(100/col)+'%'}"
             v-for="(book,index1) in item"
             :key="index1">
          <div class="home-book-wrapper"
               :style="{flexDirection : Mode ==='col' ?'row':'column'}"
               v-if=" Mode==='row'|| Mode==='col'">
            <van-image @click="ToDetail(book)" v-if="Mode === 'row' && colStyle"  width="101" height="147" fit="cover" lazy-load :src="book.cover"></van-image>
              <van-image @click="ToDetail(book)" v-if="Mode === 'row' && !colStyle"  width="68" height="99" fit="cover" lazy-load :src="book.cover"></van-image>
             <van-image @click="ToDetail(book)" v-if="Mode === 'col'" width="64" height="88" fit="cover" lazy-load :src="book.cover"></van-image>
            <div class="book-title-wrapper book-title-row"
                 v-if="Mode === 'row'">
              <div class="book-title">
                {{book.title}}
              </div>
            </div>
            <!-- 两栏布局 -->
            <div class="book-title-wrapper book-title-col"
                 v-if="Mode === 'col'">
              <div class="book-title">
                {{book.title}}
              </div>
              <div class="book-author">
                {{book.author}}
              </div>
              <div class="book-cate">
                {{book.categoryText}}
              </div>
            </div>
          </div>
          <div class="category-wrappper"
               @click.stop="toTypeList(book)"
               v-if="Mode==='cate'">
                   <div class="cate-text">{{book.text}}</div>
            <div class="cate-num">{{book.num}}本书</div>
          
            <div class="image-wrapper">
              <div class="img1">
                <imageview :src="book.cover"></imageview>
              </div>
              <div class="img2">
                <imageview :src="book.cover2"></imageview>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

# 利用开源库epubjs完成阅读器目录,主题,进度条,字体开发

目录部分:

思路:获取图书章节信息 ====> 编写解析目录算法,拿到一维章节数组 ====> 递归遍历降维章节数组,给每个元素添加当前层级属性,以供后面展示用

降维

// 一级目录 二级目录 三级目录 ....
function flaten(arr){
  return [].concat(...arr.map(item=>[item,...flattern(item.subitem)]))
}

添加层级属性

let nav = falten(vavigation)
function findLevel(item,level = 0){
  // 递归终止条件
  if(!item.parent){
    return level
  }else{
    // 过滤筛选出item的parent,找到之后继续递归向上找
    return find(nav.find(parentItem=>{
      parentItem.id === item.parent
    }),++level)
  }
}
nav.forEach(item=>{
  item.level = findLevel(item)
})

切换主题:

实现思路: 初始化阅读器时注册主题文件 ===> 唤起面板选择主题 ===> 阅读器实例切换主题 ===> 动态添加css文件,切换主题,每次删除之前的css文件

注册主题文件: 首先需要读取缓存是否已经设置了主题,然后在注册主题,并保存到Vuex当中

点击事件: 动态添加删除css文件(选中主题,修改Vuex当中的值,然后加载对应的css文件,每次选择之前删除之前添加到head的文件)

setTheme(theme){
  // vuex
  this.setTheme(theme)
  // 阅读器实例切换
  this.CurrentBook.rendition.theme.select(theme)
  // 保存缓存
  saveTheme(this.fileName,theme)
  // 动态添加删除css文件
  this.changeTheme()
}

function changeTheme(){
    removeAllCss();
      switch (this.defaultTheme) {
        case "Default":
          addLink(`https://store.yangxiansheng.top/theme/theme_default.css`);
          break;
        case "Eye":
          addLink(`https://store.yangxiansheng.top/theme/theme_eye.css`);
          break;
        case "Gold":
          addLink(`https://store.yangxiansheng.top/theme/theme_gold.css`);
          break;
        case "Night":
          addLink(`https://store.yangxiansheng.top/theme/theme_night.css`);
          break;
        default:
          this.saveTheme(this.defaultTheme);
          addLink(`https://store.yangxiansheng.top/theme/theme_default.css`);
          break;
      }
}
// 头部动态添加css文件
function addLink(href){
  const link = document.createElement("link")
  link.setAttribute('rel','stylesheet')
  link.setAttribute('type','text/css')
  link.setAttribute('href',href)
  document.getElementBtTagName['head'][0].appendChild(link)
}

// 删除之前添加的css文件

function removeAllCss(){
  removeLink('https://store.yangxiansheng.top/theme/theme_default.css')
  ...
}

function removeLink(href){
  // 遍历所有的link节点数组,link[i].getAttribute('href')就删除

  const link = document.getElementBtTagName('link')
  for(let i = link.length;i>=0;i--){
    if(link[i] && link[i].getAttribute('href').indexOf(href)!== -1){
      link[i].parentNode.removeChild(link[i]);
    }
  }
}

进度条拖拽:

这里主要是使用到了 vantSlider 组件,监听拖拽事件,拿到当前百分比,然后调用epub的获取当前进度的API,最后展示出来、

切换字号字体:

首先是切换字体大小,也是利用了 vantSlider 组件,设置一个字体大小的最大最小阈值,然后调用相应的API

然后是切换字体类型,这里我使用到的是阿里巴巴字体图标库,首先还是先引入他们在线地址,然后在初始化阅读器实例的时候,注册字体主题。然后这里使用到了弹出框popun展示字体,每次点击都去调用对应的API切换字体。

# 播放器部分

阅读器的开发主要还是借助到 wx.getBackgroundAudioManager,然后根据官网的相应API配合Vuex完成的开发

随机播放:

使用到了洗牌算法

function getRandomInt (min, max) {
  // 取min-max的数据
  return Math.floor(Math.random() * (max - min + 1) + min)
}

export function shuffle (arr) {
  // 当前数组值与取到的随机数组值交换 从而打乱数据
  let _arr = arr.slice()
  for (let i = 0; i < _arr.length; i++) {
    let j = getRandomInt(0, i)
    let t = _arr[i]
    _arr[i] = _arr[j]
    _arr[j] = t
  }
  return _arr
}

# student-admin

star 法则

这个项目是我大三上学期课程设计时所作,之所以把这个项目写在简历上,是因为我觉得他算得上一个值得写的项目,因为这是一个独立完成的前后端的Vue技术栈的PC端项目,并且继承了现在主流的一些框架,Vue-element-admin,element-ui等,比较综合并且具有代表性,任务是完成一个学生,教师,管理员共同维护的课程成绩,发布查看修改的一个管理系统。

我觉得本项目其实最难得部分就是路由拦截部分,因为这里我需要通过角色控制进行路由分配

写了一个router.beforeEach(async(to,form,next))进行全局路由拦截

  • 判断本地是否有token,这里我存储在了cookie
    • 有token
      • 当前访问路由是登录页,直接跳转至首页
      • 当前访问路由不是登录页,判断当前Vuex的角色roles是否存在
        • 存在,直接调用next(),访问路由
        • 不存在,调用接口获取用户信息,然后利用角色roles匹配路由表中的权限路由,然后调用router.addRoutes(),将白名单路由和权限路由表合在一起,然后放行
    • 没有token

这一部分根据roles去匹配路由表权限路由可以重点说一下,首先这里也是写了action ,前端vue-router定义了两种路由表,一个是白名单路由(无权限访问路由表),一张是权限路由表(通过设置meta上的roles属性限制权限),然后接着去写action里面的逻辑

这里的话,首先判断了下当前的roles是否是超级管理员,如果是超级管理员,直接将两张表拼在一起,因为管理员可以看到所有的页面,如果不是管理员,需要去过滤一下路由,这里过滤的话就是传入角色role,然后在权限路由表中匹配,如果找到,就说明有权限,然后就把可以访问的route放到数组中返回。之后就通过addRoutes()添加即可

# 使用element-ui完成复杂表单表格文件上传等开发

# 对接口使用模块化的形式进行封装,并完成前端项目的统一异常处理

# 使用springboot+jpa独立完成管理系统的25个接口开发

# 引入Echart库完成学生首页成绩分析大屏数据

# 解忧炸货铺

star 法则

这个项目的话

# 原生微信程序使用JWT令牌方式校验身份

首先需要明确一点,小程序是不需要微信密码登录的,因为这一点验证已经在微信登录做了。我们要做的就是去验证该用户到底有没有授权使用这个小程序,使用JWT的的作用也是为了保护用户的一些隐私功能。

具体的实现流程:

  1. 全局 app.js onlaunch时,首先校验当前token是否过期。这一步其实包含两个步骤
  • 如果本地缓存没有token,则去获取最新的token
  • 有缓存,则调用校验方法,如果过期则获取最新的token
  1. 每次获取服务器最新token的步骤:1. 通过 wx.login() 获取code码 2. 根据code码获取最新的token

# 改造原生请求库,统一异常处理和实现二次重刷机制

class Http {
  // * refetch 是否开启二次重刷token throwError 是否抛出异常
  static async request({
    url,
    data = {},
    method="GET",
    noRefetch = false,
    throwError = false
  }){
    let res
    try{
      res = await promisic(wx.request)({
        url:`${config.prodURL}${url}`,
        data,
        method,
        header:{
          "content-type":"application/json",
          // 头部携带token令牌
          'authorization':`Bearer ${wx.getStorageSync('token')}`
        }
      })
    }catch(e){
      // 检测网络异常
      if(throwError){
        throw new HttpException(-1,HttpExceptionCode[-1],null)
      }
      Http.showError(-1)
      return null
    }
    // 检测HTTP code码的异常 通常有401 403 404
    // 获取回调返回的状态码
    const HttpCode = res.statusCode.toString()
    if(HttpCode.startsWith("2")){
      return res.data
    }else{
      if(HttpCode === '401'){
        // 触发二次重刷token
          if (!noRefetch) {
            return Http._refetch({
              url,
              data,
              method,
            })
          }
      }else{
        if(throwError){
          // 后端抛出异常返回数据 赋给HttpException
          throw new HttpException(res.data.code,res.data.message,HttpCode)
        }
        if(HttpCode === '404'){
          // 通常404 不会抛出异常
          return isArray ? []:null
        }

        //*  异常抛出时(这里是前端的error_code码 并不是后端的code码) 加入提示 
        const error_code = res.data.error_code
        Http.showError(error_code,res.data)
      }
    }
  }
  // 二次重刷
  static async _refetch(data){
    const token = new Token()
    await token.getTokenFromServer()
    data.refetch = false
    return await Http.request(data)
  }
  // 提示气泡  : 默认气泡 config定义有气泡提示 没有的气泡提示
  // error_code 错误码 error_data 抛出异常返回的data
 static showError(error_code,error_data){
    let tip
    if(!error_code){
      tip = HttpExceptionCode[9999]
    }else{
      if(HttpExceptionCode[error_code]){
        tip = HttpExceptionCode[error_code]
      }else{
        tip = error_data.message
      }
    }
    wx.showToast({
      title: tip,
      icon: 'none',
      duration:3000
    })
  }
}

# 使用锁思想对触底加载更多需求抽象出分页请求方法

data:return {
  isEnd: false,
  lock:false
}

async getMoreData(){
  if(isEnd){
    return
  }
  if(lock){
    return
  }
  this.lock = true
  const res = await getAPI()
  this.lock = false
  if(res.code === 200){
    if(res.data.limit < conifg.limit){
      // 最后一页了
      this.isEnd = true
    }
    if(res.data.length === 0){
      this.list = res.data
    }else{
      this.list = [...(this.list,res.data)]
    }
  }
}

# 对返回的SPU和对应的SKU数据进行改造,完成SKU面板展示和联动选择效果

SPU和SKU关系梳理

SPU(Standard Product Unit) 标准化产品 -商品

SKU(Stock Keeping Unit) 库存量单位 -商品的规格,单品

规格:
颜色: 暗夜绿 黑色
运存: 64GB 256GB 
版本 : 全网通 电信

规格名:颜色
规格值 : 暗夜绿 黑色

利用面向对象的思想改造服务端数据,清晰结构方便使用

利用矩阵和排列组合完成联动效果

在sku的面板上选择规格,然后达到这种sku禁用和可选效果,其实就像我们去词典当中查单词,如果找到了单词那就是可选,没找到那就是不可选。

所以首先获取规格矩阵组成的sku总路径排列情况

然后获取已选节点的潜在路径,就是当前选中节点的所有待确认路径

最后通过比对潜在路径是否在全路径中存在,来改变单元格的状态,从而达到联调效果

获取规格矩阵组成的sku总路径排列情况

获取已选元素的潜在路径

# 合理使用Storage对购物车模块进行封装,并且实现本地和服务端同步

这里也是通过抽象出 cart 模型,对购物车面向对象化,然后在里面封装一系列的方法。

待编写。。。

Last update: 3/4/2021, 10:31:14 PM