分类
javascript vue 性能 算法 解决方案

做虚拟列表时所遇到的坑

页面跳动

出现这种情况的原因是虚拟列表所渲染出的Dom是有限的,向下滚动时需要将上方的Dom移除,向上滚动时需要添加上方的Dom

解决方案

降低Dom渲染的频率

  1. 我有一个当前所处位置的检测值命名为topIndex,以3的倍数进行检测,当我所处位置为1值为0,所处位置为4值为3
  2. 我有一个判断当前滚动方向的检测值,命名为slideArrow,当向下滑动屏幕是置为down,向上滑动屏幕时值为up
  3. 我有一个由topIndex和slideArrow组合而成的计算值,命名为topAndArrowChange
  4. 监听topAndArrowChange,当发生改变时判断滑动方向,改变虚拟列表的值
  5. 当快要到底部时会触发查找列表,数据会放进源列表recommendList中
  6. 当滑动方向为up向上滑动,看下面的内容。或者滑动方向为down向下滑动,看上面的内容。那么虚拟列表为源数据的截取值。
topAndArrowChange(newVal) {
      const circle = 30
      const circleNum = parseInt(this.topIndex / circle, 10)
      console.log(newVal)
      if (this.slideArrow === 'up') {
        this.virtualRecommendList = this.recommendList.slice(circleNum * circle, circleNum * circle + 60)
      } else {
        const start = Math.max((Math.round(this.topIndex / circle) - 1) * circle, 0)
        const end = Math.round(this.topIndex / circle) * circle + circle
        this.virtualRecommendList = this.recommendList.slice(start, end)
      }
    },

看下面的内容,截取的部分

如果当前的topIndex为40,则截取30至90,最大截取60条

看上面的内容,截取的部分

如果当前的topIndex是0,则截取0-30

如果当前的topIndex是36,则截取0-60

如果当前的topIndex是119,则截取90-150

如果当前的topIndex是129,则截取90-150

无限触发查找数据

出现原因是初始时虚拟列表没有赋值,滚动过快触底,虚拟列表来不急赋值

解决方案

  1. 添加源数据列表监听,发现源数据列表更新的同时修改虚拟列表的值
  2. 只有在查找数据时才会触发源列表数据更新,因此虚拟列表的截取值为
 // 防止无限触发查找列表
    recommendList() {
      const circle = 30
      const circleNum = parseInt(this.topIndex / circle, 10)
      this.virtualRecommendList = this.recommendList.slice(circleNum * circle)
    },

当还是以30为一个循环,最多截取不超过60个Dom

总结

综上,此方案虚拟列表最多渲染60条数据

分类
解决方案

fiddler抓手机包

思路

  • 电脑和手机在同一个网络
  • 开启fiddler查找电脑ip和fildder服务端口
  • 手机打开wlan的连接网络,长按修改网络并设置代理
  • 手机浏览器查找ip:fildder服务端口下载证书
  • 手机回到设置代理处选择CA证书为使用系统证书
  • 从fiddler查询包
qi ye wei xin jie tu 16183992113947 1024x174 - fiddler抓手机包
电脑ip
qi ye wei xin jie tu 16183996739834 - fiddler抓手机包
tools -> options -> connections查询fiddler服务端口
c660794a7242bd204908388ab3013b7f 140x300 - fiddler抓手机包
长按修改网络
a5921f028667f5627d828d08e9d51d66 479x1024 - fiddler抓手机包
修改代理
ccd7e16866f86ddc62d3cf5f4500a846 1024x541 - fiddler抓手机包
手机浏览器查找ip:fildder服务端口下载证书
eff87595726bf703933d834aec8bfe54 479x1024 - fiddler抓手机包
使用证书,填写域名,完成抓包

关闭电脑防火墙!关闭电脑防火墙!关闭电脑防火墙!

分类
解决方案

加密解决方案

此方案就算源码泄露,内容也不会泄露,且能够一次性完成全部post接口加密。前端使用加密库:rsa(jsencrypt),3des(crypto-js)

客户端向后端传输加密

采用整体post加密方案。

  1. 项目打开时是动态生成3des秘钥
  2. 发送请求时用3des给明文加密
  3. 用rsa加密3des秘钥
  4. 被加密的秘钥随请求传给后端,此秘钥可以放在headers中
  5. 后端接收3des秘钥,用rsa私钥解密
  6. 用解密后的3des秘钥给密文解密

后端向客户端传输加密

  1. 每个接口判断headers中是否有3des秘钥标识
  2. 若有获取此标识用另一个rsa私钥解密获得后端的3des秘钥
  3. 用3des秘钥给密文解密

遇到问题

前端公钥解密不了3des经过java私钥加密过的秘钥

前端用私钥来解密,后端生成另一个公钥来加密,后端的公钥不会暴露。

前端解不了后端加的密

看手否是同一种加密模式,是否是同一种填充方案,输出的是UTF-8还是BASE64

分类
解决方案

国际化解决方案(vue)

目录

  1. 国际化自定义配置
  2. 项目中使用需要注意的地方
  3. 国际化过程中节约时间的小插件
  4. 组件库的国际化

国际化配置

i18n下载

npm i vue-i18n

引入

main.js文件配置

// main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
// 国际化
import i18n from './i18n';
// 启动程序
import { created } from './core/bootstrap';

new Vue({
  router,
  store,
  created,
  i18n,
  render: h => h(App),
}).$mount('#app');

i18n目录情况

./src/i18n/
├── index.js
└── lang
    ├── README.md
    ├── en-GB                                # 英式英文 文件夹
    │   ├── business
    │   ├── common
    │   │   └── index.js                   # 通用信息
    │   └── pages                           # 项目页面标签
    │       ├── create.js                   # 新建流程页面
    │       ├── login.js                    # 登录页面
    │       ├── todo.js                     # 待办流程页面
    │       └── view.js                     # 我的申请页面
    ├── en-US                                # 美式英文 文件夹
    │   ├── business
    │   ├── common
    │   │   └── index.js
    │   └── pages
    │       ├── create.js
    │       ├── login.js
    │       ├── todo.js
    │       └── view.js
    └── zh-CN                               # 简体中文 文件夹
        ├── business
        ├── common
        │   └── index.js
        └── pages
            ├── create.js
            ├── login.js
            ├── todo.js
            └── view.js

i18n文件夹下的index.js文件配置国际化详细信息

import Vue from 'vue';
import VueI18n from 'vue-i18n';

import zhCN from './lang/zh-CN';
import enUS from './lang/en-US';
import enGB from './lang/en-GB';

Vue.use(VueI18n);

const messages = {
  'zh-CN': zhCN,
  'en-US': enUS,
  'en-GB': enGB,
};
export const defaultLang = 'zh-CN';
const i18n = new VueI18n({
  // 当前语言,从缓存中读取
  locale: defaultLang,
  // 回退语言-当找不到时,使用回退语言
  // fallbackLocale: defaultLang,
  messages,
});

export default i18n;

function setI18nLanguage(lang) {
  i18n.locale = lang;
  document.querySelector('html').setAttribute('lang', lang.toLowerCase());
  return lang;
}

export function loadLanguageAsync(lang = defaultLang) {
  return new Promise((resolve) => {
    // 缓存语言设置
    if (i18n.locale !== lang) {
      return resolve(setI18nLanguage(lang));
    }
    return resolve(lang);
  });
}

i18n->lang->en-GB->index.js

import login from './login';
import create from './create';
import personCenter from './personCenter';

export default {
  login,
  create,
  personCenter,
};

i18n->lang->en-GB->login.js

export default {
  title: 'welcome login BPM system',
  bt: 'Login',
  placeholder: {
    username: 'please input user name',
    password: 'please input password',
  },
  message: {
    username: 'please input user name',
    password: 'please input password',
  },
  error: 'login fail',
};

流程图-执行流程

qi ye wei xin jie tu 16056018244734 - 国际化解决方案(vue)

启动事件(core => bootstrap.js)

import store from '@/store/';
import auth from '@/utils/auth';
// 应用程序事件处理
export function created() {
  // 从本地存储中恢复语言设置
  store.dispatch('system/app/setLang', auth.getLocal());
}

获取本地语言函数(utils=>auth.js)

export default {
  /**
   * 获取当前语言
   */
  getLocal() {
    return localStorage.local || 'zh-CN';
  },
  /**
   * 设置语言
   */
  setLocal(local) {
    if (localStorage.local !== local) {
      localStorage.local = local;
    }
  },
};

执行国际化函数(store=>system=>app.js)

import {
  // i18n
  APP_LANGUAGE,
} from '@/store/mutation-types';
import auth from '@/utils/auth';
import { loadLanguageAsync } from '@/i18n';
const app = {
  namespaced: true,
  state: {
    // 切换语言
    lang: 'zh-CN',
  },
  mutations: {
    /**
     * 设置语言
     * @param {*} state
     * @param {*} lang
     */
    [APP_LANGUAGE](state, lang) {
      state.lang = lang;
      auth.setLocal(lang);
    },
  },
  actions: {
    setLang({ commit }, lang) {
      return new Promise((resolve, reject) => {
        commit(APP_LANGUAGE, lang);
        loadLanguageAsync(lang).then(() => {
          resolve();
        }).catch((e) => {
          reject(e);
        });
      });
    },
  },
};

export default app;

语言配置名称(store=>mutation-types.js)

export const APP_LANGUAGE = 'app_language';

在项目中进行语言切换

this.$store.dispatch('system/app/setLang', val);

项目中使用需要注意的地方

浏览页标题

某个路由

import loaded from '@/utils/util.import';

export default [
  // 默认页面
  {
    path: '/home',
    name: 'home',
    meta: {
      title: '主页',
      dataKey: 'common.home',// 若使用国际化用这个字段
      tabId: 'home',
      icon: 'home',
      closable: false,
    },
    component: loaded('Home'),
  },
  // 刷新页面 必须保留
  {
    path: '/refresh',
    hidden: true,
    component: loaded('system/function/refresh'),
  },
];

路由拦截函数

import i18n from '@/i18n';
export function routerAfterEachFunc(to) {
  util.title(i18n.t(to.meta.dataKey));
}

一般表格表头-可以放在计算属性中

computed: {
    columns() {
      const columns = [
        {
          title: this.$t('pages.create.columns.sn'),
          align: 'center',
          customRender: (text, record, index) => (this.pageNum - 1) * this.pageSize + index + 1,
        },
        {
          title: this.$t('pages.create.columns.subject'),
          dataIndex: 'subject',
        },
        {
          title: this.$t('pages.create.columns.name'),
          scopedSlots: { customRender: 'lineEllipsis' },
          dataIndex: 'processNameCn',
        },
        {
          title: this.$t('pages.create.columns.createTime'),
          dataIndex: 'createTime',
        },
        {
          title: this.$t('pages.create.columns.action'),
          key: 'action',
          width: 100,
          scopedSlots: { customRender: 'action' },
        },
      ];
      return columns;
    },
  },

2121-04-09更新

columns还是可以放在data中,但是计算属性进行变更

langColumns() {
      this.columns.forEach((item) => {
        item.title = this.$t(`columns.${item.dataIndex}`);
      });
      return this.columns;
    },

动态列表格表头-使用slot

data() {
    return {
        columns: [{
            slots: { title: 'noTitle' },
            dataIndex: 'no',
        }]
    }
}
<a-table :columns="columns">
    <span slot="noTitle">{{$t('columns.sn')}}</span>
</a-table>

国际化过程中节约时间的小插件

流程如下

vscode搜索中文:(.[\u4E00-\u9FA5]+)|([\u4E00-\u9FA5]+.)

qi ye wei xin jie tu a206e6d2cc1342e0b98fc6891bef73bf 569x1024 - 国际化解决方案(vue)

从vscode中找出所有含有汉字的代码->用小插件找到去重的汉字列表给专业人员翻译->开发人员自顾自的编写i18n文件->专业人员翻译好了,给出一个汉字英文对照表->使用小插件把中文i18n文件和汉字英文对照表放上去生成英文i18n文件->复制粘贴完成

guo ji hua xiao cha jian 1 1 - 国际化解决方案(vue)
guo ji hua xiao cha jian 2 - 国际化解决方案(vue)

小插件地址

https://github.com/zhuishao/i18n-plugin-hanzi

组件库的国际化

需求:没用国际化的项目默认显示中文,有国际的的项目使用该组件可配置vue-i18n国际化。

  1. 将https://github.com/zhuishao/ec-zs-components下的src目录复制到组件库中
  2. 在有语言的组件中加入mixins: [Locale], import Locale from ‘ec-zs-components/src/mixins/locale’,通过t(xx.xx.xx)使用
  3. ec-zs-components的引用其实是加了别名alias在vue.config.js中加入
 configureWebpack: {
        resolve: {
            alias: {
                'ec-zs-components': path.resolve(__dirname, './'),
            },
            extensions: ['.js', '.vue', '.json'],
        }
    },
  1. 向外发布的时候需要将语言包暴露出去以供使用在package的script中加入构建命令
"scripts": {
    "lib": "vue-cli-service build --target lib --name ec-zs-components --dest lib --entry packages/index.js && babel src/locale/lang --out-dir lib"
  },

在命令行执行babel命令需要加入babel-cli依赖

使用这个组件库并与i18n产生链接

在main.js国际化配置的基础上引入插件

import i18n from './i18n';
import EcZsComponent from 'ec-zs-components';
import 'ec-zs-components/lib/ec-zs-components.css';
Vue.use(EcZsComponent, {
  i18n: (key, value) => i18n.t(key, value)
});
分类
解决方案

路由控制解决方案

权限,路由

lu you kong zhi liu cheng tu 647x1024 - 路由控制解决方案
路由控制流程图
deng lu yi ge you quan xian de ye mian 647x1024 - 路由控制解决方案
登录一个有权限的页面

q1: 在哪里进行路由拦截?

每次路由跳转之前进行拦截

// @/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
// 页面路由
import routes from './routes';
// 路由拦截函数
import { routerBeforeEachFunc } from '@/config/interceptors/router';

Vue.use(Router);
const routerInstance = new Router({
  routes,
});
// 注入拦截器
routerInstance.beforeEach(routerBeforeEachFunc);
export default routerInstance;

main.js

// 获取@/router/index.js的routerInstance
import router from './router';
new Vue({
  router,
  render: h => h(App),
}).$mount('#app');

q2:是否需要控制这个判断有必要吗?

可有可无,这是作为一个框架来思考,或许某些项目不需要对路由进行控制,也没有菜单按钮权限管理等需求,就这可设置配置项为不需要控制路由。

q3:检查是否处理过权限与路由这个判断会以怎样的方式触发两种情况?

这个判断是以store中的一个属性值来判断的,默认值为false,因此会有这两种情况:1.已经登录过,在路由之间进行跳转。2.曾经登录过。重新打开浏览器,输入路由地址。

q4:如何存储token?

localStorage

q5:在有token的情况下,如何会因为异常跳转到login页?

调用用户接口失败或调动菜单接口失败

q6:在login页,怎样判断是重定向的路由还是就是在login的路由?

const redirect = decodeURIComponent(from.query.redirect || to.path);

q7:是否有权限函数做了什么?

我们在vue-router中定义了一些路由,某些路由需要权限的判定(页面权限,按钮权限),当拦截的路由符合权限判定的条件时,判断它是否有页面权限和按钮权限,而获取菜单权限时同时会获取到按钮权限,我们通过按钮权限进行一一比对得出该页面,该用户最终的权限。