2020-07 前端技術(shù)匯總

2020/07/30 周四

#什么是好的代碼?

在web前端方面,什么是好的代碼?好的代碼應(yīng)該包含以下兩個(gè)特性

  • 高性能,低時(shí)延(性能優(yōu)化)
    • 熟悉數(shù)據(jù)結(jié)構(gòu)與算法,減少時(shí)間復(fù)雜度或空間復(fù)雜度
    • 熟悉瀏覽器渲染基本原理、熟悉HTTP請(qǐng)求與響應(yīng)細(xì)節(jié)、熟悉前端框架源碼、減少不必要的渲染開(kāi)銷(xiāo),提高加載速度
  • 可讀性、可維護(hù)性、可擴(kuò)展性
    • 熟悉設(shè)計(jì)模式,封裝變化。代碼高內(nèi)聚、低耦合、指責(zé)單一、高度復(fù)用。寫(xiě)出好維護(hù)、好迭代、好擴(kuò)展的代碼
    • 化繁為簡(jiǎn),形成特定代碼規(guī)范,注意命名、注釋。寫(xiě)出人能看懂的代碼,不做騷操作。盡量保持簡(jiǎn)單、易懂,在可擴(kuò)展性和簡(jiǎn)單之間尋找平衡

前端只要不是寫(xiě)框架,性能問(wèn)題會(huì)很少遇到。簡(jiǎn)單來(lái)講,在實(shí)現(xiàn)功能的基礎(chǔ)上,代碼簡(jiǎn)單、易懂、好維護(hù)迭代就很好了。技術(shù)始終是為業(yè)務(wù)需求服務(wù)的?;A(chǔ)建設(shè)是很重要的一個(gè)環(huán)節(jié),這樣有利于快速迭代開(kāi)發(fā)

#vue使用js顯示彈窗組件

重點(diǎn)是使用vue的render函數(shù),把單文件vue組件,再封裝為一個(gè)函數(shù),掛載到vue的實(shí)例屬性后,其他地方直接調(diào)用該函數(shù)即可調(diào)用組件。

// 在main.js里注冊(cè)實(shí)例屬性
import showDialog from '@/views/jsDialog/index.js'
Vue.prototype.$showDialog = showDialog

// 其他地方直接使用 this.$showDialog(options) 即可調(diào)用組件

下面來(lái)看看實(shí)現(xiàn)思路,關(guān)于render函數(shù)createElement的options的配置,參見(jiàn) createElement 參數(shù) - 深入數(shù)據(jù)對(duì)象(opens new window)

import Vue from "vue";
import DialogComponent from '@/views/jsDialog/src/index.vue'

let TheDialog = null
export default function showDialog(options) {
  // 如果未移除,先移除
  TheDialog && TheDialog.remove()

  TheDialog = create(DialogComponent, {
    on: {
      // 單文件組件內(nèi)部可以emit該事件,銷(xiāo)毀TheDialog組件
      'close-dialog': () => {
        TheDialog.remove()
      }
    },
    props: {
      // 需要傳入的屬性,單文件組件需要使用props接收
      title: '標(biāo)題',
      content: '內(nèi)容' 
    }
    // 其他參數(shù)
    ...options
  })

  function create(Component, options) {
    // 先創(chuàng)建實(shí)例
    const vm = new Vue({
      render(h) {
        // h就是createElement,它返回VNode
        return h(Component, options);
      }
    }).$mount();

    // 手動(dòng)掛載
    document.body.appendChild(vm.$el);

    // 銷(xiāo)毀方法
    const comp = vm.$children[0];
    comp.remove = function() {
      document.body.removeChild(vm.$el);
      vm.$destroy();
    };
    return comp;
  }
}

#2020/07/26 周日

#echarts餅圖label兩端對(duì)齊label距離引導(dǎo)線距離

注意echarts版本要是 v4.6 +

// 兩端對(duì)齊 + 引導(dǎo)線距離
{
name: '訪問(wèn)來(lái)源',
type: 'pie',
minAngle: 90, // label最小扇區(qū)大小
label: {
    normal: {
        alignTo: 'edge', // label兩端對(duì)稱(chēng)布局
        //  ECharts v4.6.0 版本起,提供了 'labelLine' 與 'edge' 兩種新的布局方式
        margin: 90, // 布局為兩端對(duì)稱(chēng)時(shí)候需要外邊距防止圖表變形 數(shù)值隨意不要太大
        distanceToLabelLine: 0, // label距離引導(dǎo)線距離
        formatter: function(param) {
            return '{a|' + param.name + '}\n{hr|}\n' + '{d|' + param.value + '}';
        },
        rich: {
            a: {
                padding: [4, 10, 0, 10],  // 4邊距是文字和hr間距,此處的邊距10用于解決label和引導(dǎo)線有間距問(wèn)題
                color: 'blue'
            },
            d: {
                padding: [0, 10, 4, 10],
                color: 'purple'
            },
            hr: {
                borderWidth: 1,
                width: '100%',
                height: 0,
                borderColor: ' '
            }
        }
    },

}

// 分隔線上線顯示內(nèi)容 
label: {
    normal: {
        formatter: '{font|{c}}\n{hr|}\n{font|u0z1t8os%}',
        rich: {
            font: {
                fontSize: 20,
                padding: [5, 0],
                color: '#fff'
            },
            hr: {
                height: 0,
                borderWidth: 1,
                width: '100%',
                borderColor: '#fff'
            }
        }
    },
},
labelLine: {
    lineStyle: {
        color: '#fff'
    }
}

參考:

#element表單中,人數(shù)輸入框怎么限制只能輸入正整數(shù)

在人數(shù)這一欄,輸入時(shí),前端需要確保輸入的只能是正整數(shù),且不能是負(fù)數(shù),且自動(dòng)校正,來(lái)看看怎么實(shí)現(xiàn)

<template>
  <div>
    只能輸入正整數(shù): {{ peopleCount }}
    <el-input
      v-model="peopleCount"
      @keyup.native="keyUp"
      style="width:200px;margin:50px;"
    ></el-input>
  </div>
</template>

<script>
export default {
  data() {
    return {
      peopleCount: ""
    };
  },
  methods: {
    keyUp(e) {
      // 非數(shù)字全部轉(zhuǎn)換為''
      e.target.value = e.target.value.replace(/[^\d]/g, "");
      // 開(kāi)始的0處理
      if ([0, "0"].includes(e.target.value)) {
        e.target.value = "";
      }
      this.peopleCount = e.target.value;
      return e.target.value;
    }
  }
};
</script>

有了上面的思路后,對(duì)于萬(wàn)元輸入框怎么限制只能輸入最多保留兩位小數(shù)點(diǎn)的number類(lèi)型數(shù)據(jù),可以思考下

#怎么開(kāi)發(fā)vscode插件

在vue-cli項(xiàng)目中,每次修改vue.config.js都需要手動(dòng)停止在運(yùn)行,怎么一鍵就搞定呢?能不能開(kāi)發(fā)個(gè)vscode插件

帶著這個(gè)問(wèn)題,來(lái)看看vscode插件的開(kāi)發(fā)。直接找vscode官方教程。按照文檔先來(lái)跑一個(gè)hello word

# Install Yeoman and VS Code Extension Generator with:
npm install -g yo generator-code

運(yùn)行yo code,生成一個(gè)腳手架項(xiàng)目

guoqzuo-mac:vscodeExtension kevin$ yo code

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------′   │        generator!        │
    ( _′U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ′   `  |° ′ Y ` 

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? zuo-restart
? What's the identifier of your extension? zuo-restart
? What's the description of your extension? A plugin use to auto restart vue.con
fig.js
? Initialize a git repository? Yes
? Which package manager to use? npm

Your extension zuo-restart has been created!

To start editing with Visual Studio Code, use the following commands:

     cd zuo-restart
     code .

Open vsc-extension-quickstart.md inside the new extension for further instructions
on how to modify, test and publish your extension.

For more information, also visit http://code.visualstudio.com and follow us @code.

這樣會(huì)創(chuàng)建一個(gè)空的項(xiàng)目,只注冊(cè)了helloworld命令,我們按照 vsc-extension-quickstart.md 里的說(shuō)明運(yùn)行demo

按F5,進(jìn)入如下頁(yè)面,但并沒(méi)有像官網(wǎng)上的視頻那樣彈一個(gè)新的插件調(diào)試窗口,一直在運(yùn)行中

vscode_plugin_1.png

網(wǎng)上說(shuō)要裝一個(gè) run code的 vscode插件,也裝了。后面發(fā)現(xiàn)還是不行,點(diǎn)擊正在生成,ctrl + c 就彈出一個(gè)名為 "擴(kuò)展開(kāi)發(fā)宿主" 的新窗口了,里面可以調(diào)試插件,如下圖

vscode_plugin_2.png

生成項(xiàng)目的入口是 extension.ts,他默認(rèn)注冊(cè)了一個(gè)helloword命令,我們輸入命令就會(huì)顯示一個(gè)彈窗消息

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "zuo-restart" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with registerCommand
    // The commandId parameter must match the command field in package.json
    let disposable = vscode.commands.registerCommand('zuo-restart.helloWorld', () => {
        // The code you place here will be executed every time your command is executed

        // Display a message box to the user
        vscode.window.showInformationMessage('Hello World from zuo-restart!');
    });

    context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
export function deactivate() {}

我們?cè)诓寮{(diào)試窗口跑下hello world命令

vscode_plugin_3.png

出現(xiàn)如下彈窗消息,就說(shuō)明跑的沒(méi)問(wèn)題了

vscode_plugin_4.png

這樣hellowrod就跑起來(lái)了,vsc-extension-quickstart.md 里面有構(gòu)架、發(fā)布插件的文檔說(shuō)明

官網(wǎng)提供了一些簡(jiǎn)單的demo,可以練練手,vscode-extension-samples | github (opens new window),后續(xù)有時(shí)間了繼續(xù)研究

參考:

#loadash節(jié)流與防抖理解

理論上throttle節(jié)流一般用于像監(jiān)聽(tīng)resize方法,想要減少執(zhí)行頻率時(shí)使用。對(duì)于點(diǎn)擊按鈕提交,防止短時(shí)間內(nèi)多次點(diǎn)擊可以用防抖

但實(shí)際使用時(shí)可根據(jù)具體情況來(lái)看,本質(zhì)上都是利用setTimeout來(lái)處理執(zhí)行頻率或執(zhí)行間隔。下面是一個(gè)簡(jiǎn)單的防抖示例

import { debounce } from 'loadsh'
export default {
  methods: {
    submitFormDebounce: debounce(function() {
      console.log('submit', +new Date())
      this.submitForm()
    }, 300, {trailing: true}),

    submitForm() {

    }
  }
}

#element源碼中節(jié)流與防抖的應(yīng)用

在做input搜索時(shí),由于input change后需要請(qǐng)求接口,這里el-autocomplete有個(gè)默認(rèn)的300豪秒debounce,可以減少請(qǐng)求頻率。理論上這里減少頻率需要使用節(jié)流,但為什么是防抖呢?

我們把element源碼中對(duì)節(jié)流防抖的使用都找一找??梢钥吹絜lement使用的節(jié)流防抖庫(kù)是 throttle-debounce

發(fā)現(xiàn)節(jié)流throttle用的比較少,只找到了三個(gè)地方:

// Backtop 回到頂部
// packages/backtop/src/main.vue  滾動(dòng)監(jiān)聽(tīng)時(shí)用到了節(jié)流
import throttle from 'throttle-debounce/throttle';
mounted() {
  this.init();
  this.throttledScrollHandler = throttle(300, this.onScroll);
  this.container.addEventListener('scroll', this.throttledScrollHandler);
},

// Carousel 走馬燈 
// packages/carouse/src/main.vue 鼠標(biāo)hover,箭頭點(diǎn)擊使用了節(jié)流
import throttle from 'throttle-debounce/throttle';
created() {
  this.throttledArrowClick = throttle(300, true, index => {
    this.setActiveItem(index);
  });
  this.throttledIndicatorHover = throttle(300, index => {
    this.handleIndicatorHover(index);
  });
},

// Image 圖片 滾動(dòng)到區(qū)域懶加載時(shí),使用了節(jié)流
if (_scrollContainer) {
  this._scrollContainer = _scrollContainer;
  this._lazyLoadHandler = throttle(200, this.handleLazyLoad);
  on(_scrollContainer, 'scroll', this._lazyLoadHandler);
  this.handleLazyLoad();
}

再來(lái)看看防抖的地方

el_debounce.png

總結(jié):涉及到接口請(qǐng)求的基本都是防抖,對(duì)于不請(qǐng)求接口,防止多次執(zhí)行的情況才用節(jié)流,其他請(qǐng)求一律防抖

#log2n對(duì)數(shù)在前端的應(yīng)用場(chǎng)景:把文件大小或金額自動(dòng)添加合適的單位

在寫(xiě)下載/導(dǎo)出文件接口時(shí),由于接口文件數(shù)據(jù)是流的形式而非buffer,導(dǎo)致total為0,無(wú)法獲取進(jìn)度。只能通過(guò)loaded知道當(dāng)前下載了多少字節(jié)。前端顯示時(shí),怎么給出合適的單位,是KB、MB,還是G?

// Math.pow(2, 0) // B
// > Math.pow(2, 10) // KB
// > Math.pow(2, 20) // MB
// > Math.pow(2, 30) // GB
// > Math.pow(2, 40) // TB
// > Math.pow(2, 50) // PB
// 以此類(lèi)推...

可以通過(guò)對(duì)數(shù)來(lái)快速確定區(qū)間

/**
 * @description 格式化文件size
 * @param { Number } value 文件大小 B 字節(jié)
 * @returns 轉(zhuǎn)換后的文件大小及單位數(shù)組,保留兩位小數(shù)
 * @example
 * formatFileSize(100) =>  [100, "B"]
 * formatFileSize(10000) => [9.77, "KB"]
 * formatFileSize(100000000) => [95.37, "MB"]
 */
function formatFileSize(value) {
  let unitArr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB','YB']
  let index = Math.floor(Math.log2(value) / 10) // 計(jì)算該value的值為2的多少次方,向下取整
  // 如果超出范圍取最大值
  if (index > unitArr.length - 1) {
    index = unitArr.length - 1
  }
  let result = value / Math.pow(2, index * 10) // 裝換為合適的單位
  result = (result * 100).toFixed() / 100 

  return [result, unitArr[index]]
}

依此類(lèi)推,假設(shè)給定單位為元,將值轉(zhuǎn)換為合適的單位:元/萬(wàn)/億/兆(萬(wàn)億),10的4次方 萬(wàn),10的8次方 億,10的12次方 兆

/**
 * @description 格式化人民幣
 * @param { Number } value 元
 * @returns 轉(zhuǎn)換后的人民幣及單位數(shù)組,保留兩位小數(shù)
 * @example
 * formartMoney(1000) => [1000, "元"]
 * formartMoney(98000) => [9.8, "萬(wàn)"]
 * formartMoney(100000000) => [1, "億"]
 */
function formartMoney(value) {
  let unitArr = ['元', '萬(wàn)', '億', '兆'] 
  let index = Math.floor(Math.log10(value) / 4)
  // 如果超出范圍取最大值
  if (index > unitArr.length - 1) {
    index = unitArr.length - 1
  }
  let result = value / Math.pow(10, index * 4) // 裝換為合適的單位

  result = (result * 100).toFixed() / 100 

  return [result, unitArr[index]]
}

擴(kuò)展:

#關(guān)于常用組件、樣式、工具函數(shù)封裝代碼快速?gòu)?fù)用的思考

怎么讓搬磚更有效率,整理常用工具庫(kù)utils、搜集各種場(chǎng)景常用的代碼片段,快速ctrl+c、ctrl+v

接口請(qǐng)求、表單校驗(yàn)(正則)、表單/表格/通用樣式、表格分頁(yè)、圖表等常用的碎片化代碼搜集整理,形成文檔,以便快速?gòu)?fù)用

#2020/07/25 周六

#el-input類(lèi)型為textarea時(shí)不能使用v-model.trim

el-input如果type為textarea,不能使用.trim修飾符,否則輸入內(nèi)容時(shí)會(huì)無(wú)法換行,如果需要去掉收尾空格,可以在提交數(shù)據(jù)時(shí),手動(dòng)執(zhí)行.trim()去空格

<template>
  <div>
    <el-input
      type="textarea"
      v-model.trim="text"
      rows="5"
      style="width:200px;margin:100px;"
    ></el-input>
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: ""
    };
  }
};
</script>

#2020/07/24 周四

#git修改上上次的commit備注信息

由于提交代碼時(shí)有鉤子函數(shù),信息里面沒(méi)有包含前置的code會(huì)無(wú)法提交。所以如果commit信息寫(xiě)的有問(wèn)題需要修改后才能提交

對(duì)于修改上一次commit備注信息,我們可以使用 --amend -m 來(lái)修改。但它無(wú)法修改上上次提交信息,這種情況我們可以使用rebase來(lái)做處理,下面來(lái)做一個(gè)測(cè)試

本地做兩次提交,第一次提交信息為"測(cè)試第一次提交", 第二次提交信息為 "第二次提交",先不push,我們需要修改上上次的提交信息,也就是修改"測(cè)試第一次提交"的內(nèi)容

# 查看git記錄
guoqzuo-mac:fedemo kevin$ git log
commit 3814855781da539d21e2072e42a53558587497c6 (HEAD -> master)
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 22:28:15 2020 +0800

    第二次提交

commit e889c7ecbcb024037701eb48c9bfe3b9c22f9490
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 20:04:27 2020 +0800

    測(cè)試第一次提交

commit d5c2f2f3193cf02d6ac1ae995ca00c4082e36cad (origin/master, origin/HEAD)
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 00:56:34 2020 +0800

    update cookie研究,合并單元格研究demo
:

執(zhí)行 git rebase -i HEAD~2,如下圖可以看到最近兩次提交,進(jìn)入一個(gè)vim編輯頁(yè)面

edit_commit_rebase_1.png

按ESC, 再按a進(jìn)入INSERT模式, 將上上次提交的信息前的pick改為edit,如下圖,按ESC, shift + : 進(jìn)入命令模式,輸入x 或wq保存,不熟悉vim操作的可以搜索下vim教程

edit_commit_rebase_2.png

保存后,會(huì)看到下面log

guoqzuo-mac:fedemo kevin$ git rebase -i HEAD~2
Stopped at e889c7e...  測(cè)試第一次提交
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
guoqzuo-mac:fedemo kevin$ 

運(yùn)行 git commit --amend 會(huì)進(jìn)入下面的修改頁(yè)面,可以修改上上次的信息

edit_commit_rebase_3.png

這里我們把上上次信息改為 "測(cè)試第一次提交,修改第一次提交的內(nèi)容",保存后,結(jié)果如下

[detached HEAD 45f5911] 測(cè)試第一次提交,修改第一次提交的內(nèi)容
 Date: Mon Aug 10 20:04:27 2020 +0800
 1 file changed, 2 insertions(+)
guoqzuo-mac:fedemo kevin$ 

然后運(yùn)行 git rebase --continue,這樣就修改好了

guoqzuo-mac:fedemo kevin$ git rebase --continue
Successfully rebased and updated refs/heads/master.
guoqzuo-mac:fedemo kevin$ 

再來(lái)git log 看看提交記錄,修改上上次的提交信息已ok

guoqzuo-mac:fedemo kevin$ git log
commit e498bcabf2d2e4c97f47320e1d72693cb82d9db8 (HEAD -> master)
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 22:28:15 2020 +0800

    第二次提交

commit 45f591157be44100073de14f5808b816104a8f2b
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 20:04:27 2020 +0800

    測(cè)試第一次提交,修改第一次提交的內(nèi)容

參考:修改上上次的commit信息(opens new window)

#2020/07/22

#echarts動(dòng)態(tài)改變option里dataZoom的值沒(méi)有實(shí)時(shí)生效的問(wèn)題

這里我們雖然修改了options的值,但不會(huì)實(shí)時(shí)生效,需要手動(dòng)調(diào)用下echarts實(shí)例的resize()方法

另外在做echarts時(shí),對(duì)于自適應(yīng)縮放的圖表,一定要注意在窗口縮放時(shí),重新調(diào)用resize()

#http請(qǐng)求有哪幾種傳參方式

在swagger文檔里,有一個(gè)傳參類(lèi)型的描述 Parameter Type,一般有四種

  • header 通過(guò)請(qǐng)求頭傳參,也就是參數(shù)加到首部 headers 里
  • path 參數(shù)放到url路徑里,比如 /user/123 這里 123是用戶(hù)id
  • query 查詢(xún)參數(shù),也就是url后面 ? 符號(hào)之后的傳參,一般用于get請(qǐng)求傳參,比如 /user/123?a=xx&b=xx
  • body 參數(shù)放到請(qǐng)求體,一般用于post請(qǐng)求,相對(duì)get請(qǐng)求來(lái)說(shuō),安全性好,可以傳的數(shù)據(jù)更多

#Object.assgin時(shí)是否會(huì)忽略null,undefined,空字符串

一般我們?cè)谛枰O(shè)置某個(gè)對(duì)象的多個(gè)值時(shí)Object.assgin是一種很好的方法,但又怕當(dāng)某個(gè)屬性的值為空字符串、null或undefined時(shí),會(huì)自動(dòng)跳過(guò)的情況。這里來(lái)做一個(gè)簡(jiǎn)單的測(cè)試

objA = {a: 'a', b: 'b', c: 'c', d: 'd'}
Object.assign(objA, {a: 1, b: undefined, c: null, d: ''})
objA // {a: 1, b: undefined, c: null, d: ""}

Object_assign.png

綜上,Object.assgin可以放心用

#git將遠(yuǎn)程倉(cāng)庫(kù)A分支合并到B分支

假設(shè)要將遠(yuǎn)程分支的 A 分支合并到 B 分支,一般我會(huì)先在A分支將B分支merge,再切到B分支,merge A分支。

以將遠(yuǎn)程倉(cāng)庫(kù)的 dev1.3.4 分支合并到遠(yuǎn)程的 test1.3.4 分支為例,下面是我一般的合并過(guò)程

# 1\. 本地切到 dev1.3.4 分支
# 2\. merge遠(yuǎn)程的test1.3.4分支,命令如下
git merge origin/test1.3.4
# 3.如果有沖突(conflict),修改沖突文件
# 4.修改沖突后提交代碼到遠(yuǎn)程倉(cāng)庫(kù),命令如下
git add 修改沖突相關(guān)的文件
git commit '修改沖突,fix conflict'
git push
# 5.切換到test1.3.4分支
# 6.merge本地的dev1.3.4,因?yàn)楸镜氐膁ev1.3.4是最新的代碼,命令如下
git merge dev1.3.4 => git push

另外,養(yǎng)成習(xí)慣,在git push前,先git pull

#2020/07/17 周五

#前端修改cookie后,相關(guān)cookie改動(dòng)會(huì)傳到后臺(tái)嗎

首先我們來(lái)捋一捋,什么是cookie?與cookie相關(guān)的知識(shí)點(diǎn)有兩個(gè):

  1. 前端獲取/設(shè)置cookie,使用 document.cookie
  2. HTTP請(qǐng)求與響應(yīng)相關(guān)cookie

我們先下個(gè)結(jié)論:他們之間是相互關(guān)聯(lián)的,接口響應(yīng)頭設(shè)置cookie,會(huì)對(duì)document.cookie的值產(chǎn)生影響;前端設(shè)置docuemnt.cookie也會(huì)對(duì)請(qǐng)求頭cookie值產(chǎn)生影響,但如果后端寫(xiě)到前端的cookie如果使用了HttpOnly屬性,前端是無(wú)法通過(guò)document.cookie做修改的

#根據(jù)功能點(diǎn)寫(xiě)測(cè)試demo

紙上得來(lái)終覺(jué)淺,這里為了弄懂這里面的關(guān)系,我們來(lái)寫(xiě)一個(gè)demo做測(cè)試,將涉及的知識(shí)點(diǎn)都串起來(lái),首先要有一個(gè)html頁(yè)面,有兩個(gè)用處

  1. 在該頁(yè)面打開(kāi)F12,在console里通過(guò)命令查看或設(shè)置document.cookie信息
  2. 在該頁(yè)面中請(qǐng)求接口,在F12 Network里觀察請(qǐng)求頭,響應(yīng)頭里cookie的信息

另外需要寫(xiě)兩個(gè)接口,用戶(hù)觀察請(qǐng)求響應(yīng)頭里cookie的信息,這里我們用koa來(lái)寫(xiě)兩個(gè)簡(jiǎn)單的接口。

下面是demo目錄層級(jí)

# demo 目錄層級(jí)
├── public
│   └── index.html # 靜態(tài)頁(yè)面
├── index.js # 接口
└── package.json # npm init 創(chuàng)建,用于安裝index.js引入的koa等npm包

index.html代碼如下

<body>
  <h1>test測(cè)試</h1>
  <button id="userInfoBtn">獲取user信息</button>
  <button id="editInfoBtn">修改user信息</button>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    let userInfoBtn = document.querySelector('#userInfoBtn')
    let eidtInfoBtn = document.querySelector('#userInfoBtn')
    // 請(qǐng)求獲取用戶(hù)信息接口
    // 用于測(cè)試響應(yīng)頭設(shè)置 'Set-Cookie' 對(duì)前端docuemnt.cookie的影響
    userInfoBtn.onclick = () => {
      axios.get('/user').then((res) => {
        console.log(res)
      }).catch((e) => {
        console.error(e.message)
      })
    }
    // 請(qǐng)求修改用戶(hù)信息接口
    // 用于測(cè)試document.cookie設(shè)置后或者第一次請(qǐng)求響應(yīng)頭設(shè)置Set-Cookie后,對(duì)下次接口請(qǐng)求頭的影響
    editInfoBtn.onclick = () => {
      axios.put('/user').then((res) => {
        console.log(res)
      }).catch((e) => {
        console.error(e.message)
      })
    }
  </script>
</body>

index.js接口代碼

const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()

// 靜態(tài)服務(wù),用于使用 http://127.0.0.1:9000/ 訪問(wèn) public下的index.html頁(yè)面
app.use(new require('koa-static')('./public'))

// GET /user  接口,設(shè)置Set-Cookie響應(yīng)頭測(cè)試
router.get('/user', ctx => {
  ctx.set({
    // 'Set-Cookie': 'token=123;path=/;max-age=100;HttpOnly',
    'Set-Cookie': ['token=123;path=/;max-age=100;HttpOnly','mark=9;path=/;']
  })
  ctx.body = {
    name: '張三',
    age: 20
  }
})

// PUT /user  接口,用于觀察接口請(qǐng)求頭
router.put('/user', ctx => {
  ctx.body = {
    code: 0,
    data: {},
    msg: '成功'
  }
})

app.use(router.routes())

// 開(kāi)啟本地HTTP服務(wù),9000端口
app.listen(9000, () => {
  console.log('server listen on 9000 port')
})

#document.cookie設(shè)置對(duì)請(qǐng)求頭的影響

首先我們可以先看 Document.cookie - Web API 接口參考 | MDN (opens new window)這個(gè)文檔,對(duì)document.cookie有一個(gè)大概的了解。

我們先運(yùn)行demo,nodemon index.js,訪問(wèn) http://127.0.0.1:9000/ 進(jìn)入頁(yè)面,查看cookie信息,如下圖

what_cookie_1.png

這時(shí),我們發(fā)送一個(gè)put請(qǐng)求,看接口請(qǐng)求頭信息。再通過(guò) document.cookie = "a=1" 設(shè)置cookie,然后再發(fā)送一個(gè)put請(qǐng)求,對(duì)比請(qǐng)求頭之前的區(qū)別,如下圖,我們初步得出結(jié)論:前端設(shè)置cookie后,下次請(qǐng)求,在請(qǐng)求頭里會(huì)攜帶這個(gè)cookie

what_cookie_2.png

#document.cookie操作方法

document.cookie API的設(shè)計(jì)不怎么友好,一般會(huì)使用一個(gè)庫(kù),來(lái)操作docuemnt.cookie,需要注意

document.cookie = "a=1" // 如果cookie中沒(méi)有a這個(gè)key,可以添加key為a,值為1的cookie,否則修改對(duì)應(yīng)的值為1
document.cookie = "" // 不會(huì)清空cookie,對(duì)cookie基本無(wú)影響
// 怎么刪除 a=1 的cookie呢?將該cookie的有效時(shí)間設(shè)置為過(guò)去的時(shí)間即可
document.cookie = "a=1;expires=" + new Date('1970-1-1')

MDN document.cookie文檔里有推薦的一個(gè)操作cookie的封裝方法,可以參考下

/*\
|*|
|*|  :: cookies.js ::
|*|
|*|  A complete cookies reader/writer framework with full unicode support.
|*|
|*|  https://developer.mozilla.org/en-US/docs/DOM/document.cookie
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*|  Syntaxes:
|*|
|*|  * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*|  * docCookies.getItem(name)
|*|  * docCookies.removeItem(name[, path], domain)
|*|  * docCookies.hasItem(name)
|*|  * docCookies.keys()
|*|
\*/

var docCookies = {
  getItem: function (sKey) {
    return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
  },
  setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
    if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
    var sExpires = "";
    if (vEnd) {
      switch (vEnd.constructor) {
        case Number:
          sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
          break;
        case String:
          sExpires = "; expires=" + vEnd;
          break;
        case Date:
          sExpires = "; expires=" + vEnd.toUTCString();
          break;
      }
    }
    document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
    return true;
  },
  removeItem: function (sKey, sPath, sDomain) {
    if (!sKey || !this.hasItem(sKey)) { return false; }
    document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
    return true;
  },
  hasItem: function (sKey) {
    return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
  },
  keys: /* optional method: you can safely remove it! */ function () {
    var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
    for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
    return aKeys;
  }
};

#HTTP cookies后端接口向前端頁(yè)面寫(xiě)入cookie

可以先看下面兩個(gè)文檔,對(duì)http cookie有一個(gè)基本的了解

  1. http請(qǐng)求頭或響應(yīng)頭參數(shù)cookie Cookie - HTTP Headers | MDN(opens new window)
  2. http cookies HTTP cookies - HTTP | MDN(opens new window)

An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to the user's web browser. The browser may store it and send it back with later requests to the same server. Typically, it's used to tell if two requests came from the same browser — keeping a user logged-in, for example. It remembers stateful information for the stateless HTTP protocol.(HTTP Cookie 是服務(wù)器發(fā)送到用戶(hù)瀏覽器并保存在本地的一小塊數(shù)據(jù),它會(huì)在瀏覽器下次向同一服務(wù)器再發(fā)起請(qǐng)求時(shí)被攜帶并發(fā)送到服務(wù)器上。通常,它用于告知服務(wù)端兩個(gè)請(qǐng)求是否來(lái)自同一瀏覽器,如保持用戶(hù)的登錄狀態(tài)。Cookie 使基于無(wú)狀態(tài)的HTTP協(xié)議記錄穩(wěn)定的狀態(tài)信息成為了可能。)

Cookies are mainly used for three purposes(cookie主要由以下三個(gè)應(yīng)用場(chǎng)景):

  • Session management (會(huì)話(huà)管理)
    • Logins, shopping carts, game scores, or anything else the server should remember(用戶(hù)登錄狀態(tài)、購(gòu)物車(chē)、游戲分?jǐn)?shù)或其它需要記錄的信息)
  • Personalization (個(gè)性化)
    • User preferences, themes, and other settings(用戶(hù)自定義配置,主題或其他設(shè)置)
  • Tracking (用戶(hù)行為跟蹤)
    • Recording and analyzing user behavior (用于記錄和分析用戶(hù)行為)

#cookie設(shè)置屬性詳解

服務(wù)端通過(guò)在接口響應(yīng)頭設(shè)置 'Set-Cookie' ,將對(duì)應(yīng)的cookie寫(xiě)到前端,先來(lái)看看設(shè)置cookie的格式

Set-Cookie: “name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure;samesite”

屬性 說(shuō)明 默認(rèn)值
name 一個(gè)唯一確定的cookie名稱(chēng)。如有特殊字符需要編碼(encodeURIComponent) /
value 存儲(chǔ)在cookie中的字符串值。如有特殊字符需要編碼 空字符串
domain cookie對(duì)于哪個(gè)域是有效,如果指定了一個(gè)域,那么子域也包含在內(nèi)。.xx.com,對(duì)于xx.com的所有子域都有效 當(dāng)前url所在的域名
path 表示這個(gè)cookie影響到的路徑,瀏覽器跟會(huì)根據(jù)這項(xiàng)配置,像指定域中匹配的路徑在請(qǐng)求頭發(fā)送cookie信息 當(dāng)前url所在的path
expires 失效時(shí)間,是一個(gè)具體的時(shí)間,這個(gè)值是GMT時(shí)間格式,expires=" + new Date('1970-1-1') 這種即可。如果客戶(hù)端和服務(wù)器端時(shí)間不一致,使用expires就會(huì)存在偏差。注意兩點(diǎn):1.設(shè)置一個(gè)過(guò)去的時(shí)間,可用于刪除該cookie 2.當(dāng)Cookie的過(guò)期時(shí)間被設(shè)定時(shí),設(shè)定的日期和時(shí)間只與客戶(hù)端相關(guān),而不是服務(wù)端。max-age也是如此 如果沒(méi)有定義,cookie會(huì)在對(duì)話(huà)結(jié)束時(shí)(關(guān)閉瀏覽器)過(guò)期
max-age 與expires作用相同,用來(lái)告訴瀏覽器此cookie多久過(guò)期(單位是秒),而不是一個(gè)固定的時(shí)間點(diǎn)。正常情況下,max-age的優(yōu)先級(jí)高于expires。 同上面的expires
HttpOnly 告知瀏覽器不允許通過(guò)腳本document.cookie去更改這個(gè)值,同樣這個(gè)值在document.cookie中也不可見(jiàn)。但在http請(qǐng)求張仍然會(huì)攜帶這個(gè)cookie。注意這個(gè)值雖然在腳本中不可獲取,但仍然在瀏覽器安裝目錄中以文件形式存在。這項(xiàng)設(shè)置通常在服務(wù)器端設(shè)置。有助于緩解跨站點(diǎn)腳本(XSS)攻擊。 不設(shè)置
secure 安全標(biāo)志,指定后,只有在使用SSL鏈接(https)時(shí)候才能發(fā)送到服務(wù)器,如果是http鏈接則不會(huì)傳遞該信息。就算設(shè)置了secure 屬性也并不代表他人不能看到你機(jī)器本地保存的 cookie 信息,因此未加密的重要信息盡量不要放cookie了 不設(shè)置
samesite 當(dāng)跨域請(qǐng)求是瀏覽器是否發(fā)送cookie,可以阻止跨站請(qǐng)求偽造攻擊CSRF。1. 值為None: 瀏覽器會(huì)在同站請(qǐng)求、跨站請(qǐng)求下繼續(xù)發(fā)送 cookies,不區(qū)分大小寫(xiě)。2.Strict:瀏覽器將只在訪問(wèn)相同站點(diǎn)時(shí)發(fā)送 3.Lax:與 Strict 類(lèi)似,但用戶(hù)從外部站點(diǎn)導(dǎo)航至URL時(shí)(例如通過(guò)鏈接)除外。 Same-site cookies 將會(huì)為一些跨站子請(qǐng)求保留,如圖片加載或者 frames 的調(diào)用,但只有當(dāng)用戶(hù)從外部站點(diǎn)導(dǎo)航到URL時(shí)才會(huì)發(fā)送。如 link 鏈接 以默認(rèn)值都是None,現(xiàn)在基本新的瀏覽器基本都是Lax了

#后端設(shè)置'Set-Cookie'響應(yīng)頭對(duì)前端的影響

我們?cè)?GET /user 接口做了處理,當(dāng)訪問(wèn)這個(gè)接口時(shí),向前端設(shè)置cookie, 一個(gè)HttpOnly的,一個(gè)非httpOnly,看看效果

ctx.set({
  // 單個(gè)cookie設(shè)置
  // 'Set-Cookie': 'token=123;domain=;path=/;max-age=100;HttpOnly', 
  // 多個(gè)cookie設(shè)置
  'Set-Cookie': ['token=123;path=/;max-age=100;HttpOnly','mark=9;path=/;max-age=60;']
})

先刪除之前的cookie,保證document.cookie為空

what_cookie_3.png
  1. 先請(qǐng)求獲取user信息接口,在F12里看響應(yīng)頭,Respons e Headers可以看到HttpOnly的cookie
  2. 使用document.cookie看下前端cookie信息。這里獲取不到設(shè)置了HttpOnly的cookie
  3. 請(qǐng)求修改user信息接口,看請(qǐng)求頭。后端設(shè)置的cookie,下次請(qǐng)求會(huì)攜帶
  4. 100秒后,發(fā)現(xiàn)document.cookie以及application面板都沒(méi)有cookie值了,因?yàn)槲覀冊(cè)O(shè)置了有效時(shí)間,再次發(fā)送修改user的put請(qǐng)求,請(qǐng)求頭不會(huì)攜帶任何cookie信息
what_cookie_4.png
  1. 再次請(qǐng)求獲取user信息接口,然后用document.cookie修改token的值,發(fā)現(xiàn)HttpOnly屬性的cookie前端無(wú)法通過(guò)document.cookie來(lái)修改,且再次請(qǐng)求put接口,token值還是原先后端設(shè)置的值,所以HttpOnly屬性的cookie前端無(wú)法修改
what_cookie_5.png

#參考文檔

#element合并單元格利用css自定義表格border

有個(gè)較為特殊的表格,需要合并單元格,且改變表格border,看看element el-table怎么實(shí)現(xiàn)這種表格

special_table.png
<template>
  <div class="table-test">
    <el-table
      :data="dataList"
      border
      size="mini"
      :span-method="arraySpanMethod"
      :header-cell-style="{ background: '#f7f7f7' }"
    >
      <el-table-column
        v-for="item in ['a', 'b', 'c', 'd']"
        :key="item"
        :prop="item"
        :label="item"
      ></el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dataList: []
    };
  },
  mounted() {
    this.dataList = [1, 2, 3, 4].map(() => {
      return { a: "1", b: "2", c: "3", d: 4 };
    });
  },
  methods: {
    arraySpanMethod({ row, column, rowIndex, columnIndex }) {
      console.log(row, column, rowIndex, columnIndex);
      // 只是遍歷表格td內(nèi)容,不包含th表頭
      // 對(duì)第一列,進(jìn)行合并列
      if (columnIndex === 0) {
        if (rowIndex === 0) {
          // 第一列,第一行,默認(rèn)
          return {
            rowspan: 1,
            colspan: 1
          };
        } else if (rowIndex === 1) {
          // 第一列,第二行,合并,占this.dataList.length - 1行
          return {
            rowspan: this.dataList.length - 1,
            colspan: 1
          };
        } else if (rowIndex >= 2) {
          // 第一列,剩余行,為空
          return {
            rowspan: 0,
            colspan: 0
          };
        }
      }
    }
  }
};
</script>

<style lang="less" scoped>
.table-test {
  width: 500px;
  margin: 100px;
  // border處理
  // 去掉表頭單元格th右邊框
  /deep/ .el-table th:not(:first-child) {
    border-right: 0;
  }
  // 去掉表格內(nèi)容單元格td的右側(cè)邊框、底部邊框
  /deep/ .el-table td {
    border-right: 0;
    border-bottom: 0;
  }
  // 為第一行td增加底部border
  /deep/ .el-table__row:first-child td {
    border-bottom: 1px solid #eaeaea;
  }
  // 為第一行第一列td增加右側(cè)border
  /deep/ .el-table__row:first-child td:first-child,
  // 為第二行(合并后的)第一列td設(shè)置右側(cè)border
  /deep/ .el-table__row:nth-child(2) td:first-child {
    border-right: 1px solid #eaeaea;
  }
}
</style>

#2020/07/12 周日

#github clone下載太慢怎么解決

以clone vue源碼為例,默認(rèn)git clone下載非常慢,我們可以把github.com鏈接改為鏡像github.com.cnpmjs.org,這樣下載速度就很快了,過(guò)程如下

# git clone 下載vue源碼
guoqzuo-mac:source kevin$ git clone https://github.com/vuejs/vue.git
Cloning into 'vue'...
remote: Enumerating objects: 56366, done.
^Cceiving objects:   5% (2823/56366), 556.01 KiB | 10.00 KiB/s   
guoqzuo-mac:source kevin$ 
guoqzuo-mac:source kevin$ git clone https://github.com.cnpmjs.org/vuejs/vue.git
Cloning into 'vue'...
remote: Enumerating objects: 56366, done.
remote: Total 56366 (delta 0), reused 0 (delta 0), pack-reused 56366
Receiving objects: 100% (56366/56366), 26.75 MiB | 1.22 MiB/s, done.
Resolving deltas: 100% (39568/39568), done.
guoqzuo-mac:source kevin$ 

github_clone_slow.png

#js獲取location.href不真實(shí)的問(wèn)題

macOS 修改host文件 /etc/hosts 后,本地訪問(wèn)某個(gè)域名會(huì)按照host指定的ip去解析,就會(huì)造成前端location.href不準(zhǔn)確的問(wèn)題,下面來(lái)看看

# 默認(rèn)沒(méi)有寫(xiě)的權(quán)限,無(wú)法編輯
guoqzuo-mac:~ kevin$ ls -l /etc/hosts 
-rw-r--r--  1 root  wheel  244  3 24  2019 /etc/hosts
# 新增寫(xiě)的權(quán)限
guoqzuo-mac:~ kevin$ sudo chmod 0666 /etc/hosts
Password:
# 再次查看權(quán)限
guoqzuo-mac:~ kevin$ ls -l /etc/hosts 
-rw-rw-rw-  1 root  wheel  244  3 24  2019 /etc/hosts
# 使用vi修改該文件
guoqzuo-mac:~ kevin$ vi /etc/hosts
# 新增a.com解析,在本地將a.com解析到47.107.190.93的服務(wù)器,也就是zuo11.com解析到的服務(wù)器
47.107.190.93 a.com

macOS修改host是實(shí)時(shí)生效的,修改后,本地瀏覽器訪問(wèn)a.com,會(huì)訪問(wèn)47.107.190.93的服務(wù)器,顯示的是zuo11.com的內(nèi)容。這時(shí)用location.href就是a.com,而不是zuo11.com。下面是百度統(tǒng)計(jì)的數(shù)據(jù),顯示的就是a.com

a_com_#png

#2020/07/09 周四

#表格怎么畫(huà)斜線

在最近的需求中,有個(gè)表格表頭里有斜線,我特意翻了HTML5權(quán)威指南的筆記,發(fā)現(xiàn)并沒(méi)有介紹怎么畫(huà)表頭的斜線。找了下網(wǎng)上的實(shí)現(xiàn),一般都是通過(guò)css來(lái)實(shí)現(xiàn),效果如下,在線預(yù)覽地址: 表格畫(huà)斜線 | github(opens new window)

table_slash.png

基本實(shí)現(xiàn)都是純css,有的用到的偽元素。我這里直接用了三個(gè)元素來(lái)實(shí)現(xiàn),如果想要完全理解,需要用到一點(diǎn)點(diǎn)數(shù)學(xué)方面的知識(shí)。

  1. 比如計(jì)算斜線的長(zhǎng)度。勾股定理 a2 + b2 = c2
/* 斜邊邊長(zhǎng) */
/* Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) */
/* Math.sqrt(Math.pow(150, 2) + Math.pow(80, 2)) = 170 */

  1. 根據(jù)th單元格的寬高計(jì)算斜線的rotate角度。給定直角三角形的邊長(zhǎng),怎么計(jì)算角度? 這里我們知道寬高,不知道斜邊邊長(zhǎng),假設(shè)角度A,正切tanA = 對(duì)邊(高) / 鄰邊(寬),我們知道這個(gè)角度A的正切值,怎么反向計(jì)算A的角度呢。就需要用到反正切函數(shù) Math.atan了,他會(huì)返回一個(gè)弧度值。在JS中 180度對(duì)應(yīng)的值為 Math.PI,計(jì)算出來(lái)的值乘以 (180 / Math.PI) 就是可以在css中使用的度數(shù)了,單位為 deg
/* 角度計(jì)算公式 */ 
/*  Math.atan(height / width) * 180 / Math.PI  */
/*  Math.atan(80 / 150) * 180 / Math.PI  = 28.072486935852954 */

完整代碼如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* 基本表格元素 */
    table {
      border-collapse: collapse;
    }
    th,td {
      border: 1px solid #666;
      padding: 5px;
    }

    /* th單元格 */
    .slash-wrap {
      position: relative;
      box-sizing: border-box;
      width: 150px;
      height: 80px;
    }

    /* 斜線 */
    .slash {
      position: absolute;
      display: block;
      top: 0;
      left: 0;
      /* 斜邊邊長(zhǎng) */
      /* Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) */
      /* Math.sqrt(Math.pow(150, 2) + Math.pow(80, 2)) = 170 */
      width: 170px;
      height: 1px;
      background-color:#000;
      /* 旋轉(zhuǎn)角度計(jì)算公式 */ 
      /*  Math.atan(height / width) * 180 / Math.PI  */
      /*  Math.atan(80 / 150) * 180 / Math.PI  = 28.072486935852954 */
      transform: rotate(28.072486935852954deg); 
      transform-origin: top left;
    }

    /* 左下角文字 */
    .left {
      position: absolute;
      /* 左下角 left:0; bottom: 0; */
      left: 15px;
      bottom: 15px;
    }

    /* 右上角文字 */
    .right {
      position: absolute;
      /* 右上角 right:0; top: 0; */
      right: 15px;
      top: 15px;
    }
  </style>
</head>

<body>
  <div>
    <table>
      <tr>
        <th class="slash-wrap">
          <span class="left">姓名</span>
          <span class="slash"></span>
          <span class="right">科目</span>
        </th>
        <th>語(yǔ)文</th>
        <th>數(shù)學(xué)</th>
      </tr>
      <tr>
        <td>張三</td>
        <td>89</td>
        <td>80</td>
      </tr>
      <tr>
        <td>李四</td>
        <td>89</td>
        <td>80</td>
      </tr>
    </table>
  </div>
</body>

</html>

參考:

#權(quán)限code狀態(tài)管理設(shè)計(jì)

一般使用vuex狀態(tài)管理,提供一個(gè)getter方法獲取對(duì)應(yīng)的角色權(quán)限,假設(shè)getter名為roleMuster,通過(guò)mapGetters導(dǎo)入,然后使用

let { role_admin, role_a, role_b, role_c } = this.roleMuster
// 如果有對(duì)應(yīng)的權(quán)限,值則為true

在getter的邏輯里可以對(duì)角色對(duì)應(yīng)的code進(jìn)行轉(zhuǎn)換,使用好理解變量代替,消除魔術(shù)字符串

#2020/07/08 周三

#pc樣式自適應(yīng)rem

pc端如果做官網(wǎng)、展示類(lèi)的UI,就需要使用rem了。為了好還原UI圖,1rem通常設(shè)計(jì)為100px。rem是相對(duì)于html元素font-size來(lái)計(jì)算的,我們可以通過(guò)動(dòng)態(tài)的改變html元素的fontSize,來(lái)實(shí)現(xiàn)頁(yè)面自適應(yīng)

怎么動(dòng)態(tài)設(shè)置html元素的font-size呢?來(lái)看看

export default {
  created() {
    const recalc = () => {
      let designSize = 1920
      let minWidth = 1280
      let html = document.documentElement
      let w = html.clientWidth < minWidth ? minWidth : html.clientWidth
      let rem = ( w / designSize) * 100
      this.rem = rem // 當(dāng)前頁(yè)面 使用時(shí)寬高使用 this.rem * (設(shè)計(jì)稿標(biāo)記尺寸/100)
      // html.style.fontSize = `${rem}px`  // 會(huì)影響所有頁(yè)面
    }
    this.recalc = recalc
    recalc()
    // 窗口變更后,變更rem
    window.addEventListener('resize', recalc)
  },
  // 組件銷(xiāo)毀時(shí)移除resize事件的recalc
  beforeDestroy() {
    window.removeEventListener('resize', this.recalc, false)
  }
}

這里就凸顯出 window.addEventListener('resize') 相對(duì)于 window.onresize 的優(yōu)勢(shì),假設(shè)項(xiàng)目很大,其他代碼已經(jīng)監(jiān)聽(tīng)了window.onresize如果你再用window.onresize可能會(huì)覆蓋原來(lái)的方法,要特別小心,而window.addEventListener就不用擔(dān)心這個(gè)問(wèn)題,你只需要注意remove的時(shí)候,只移除你自己的監(jiān)聽(tīng)函數(shù)即可。

#background設(shè)置圖片背景相關(guān)

HTML5權(quán)威指南這本書(shū)對(duì)background的簡(jiǎn)寫(xiě)貌似有點(diǎn)不正確,使用起來(lái)會(huì)有問(wèn)題,這次讓圖片在某個(gè)區(qū)域完全顯示,是分開(kāi)寫(xiě)的,如下:

div {
  background: #fff url('/images/xxx.png') no-repeat;
  background-size: cover;
}

后面仔細(xì)測(cè)試下這塊

#移動(dòng)端rem自適應(yīng)

設(shè)計(jì)圖750 * xx(iphone 6/7/8 或 全面屏),以100為基準(zhǔn)

function initRem() {
  let html = document.documentElement
  let resizeEventName = 'orientationchange' in window ? 'orientationchange' : 'resize'
  let recalc = () => {
    let w = html.clientWidth < 320 ? 320 : html.clientWidth
    let fontSize = w > 750 ? 200 : ((w / 375) * 100)
    Object.assign(html.style, { fontSize })
  }
  recalc()
  [resizeEventName, 'DOMContentLoaded'].map(eventName => {
    document.addEventListener(eventName, recalc, false)
  })
}

#2020/07/05 周日

#vue實(shí)現(xiàn)一個(gè)tree組件

樹(shù)形組件主要是遞歸的問(wèn)題,組件自己調(diào)用自己,來(lái)寫(xiě)個(gè)簡(jiǎn)單的例子

<template>
  <div>
    <z-tree :data="treeData"></z-tree>
  </div>
</template>

<script>
export default {
  components: {
    ZTree: () => import("./ZTree")
  },
  data() {
    return {
      treeData: [
        {
          label: "冰箱"
        },
        {
          label: "水果",
          list: [
            { label: "蘋(píng)果" },
            { label: "梨子" },
            { label: "葡萄" },
            {
              label: "喜歡的水果",
              list: [{ label: "水果1" }, { label: "水果2" }, { label: "水果3" }]
            },
            { label: "香蕉" }
          ]
        },
        {
          label: "茶葉",
          list: [
            { label: "鐵觀音" },
            { label: "西湖龍井" },
            { label: "毛尖" },
            {
              label: "紅茶",
              list: [{ label: "紅茶1" }, { label: "紅茶2" }, { label: "紅茶3" }]
            }
          ]
        }
      ]
    };
  }
};
</script>

ZTree.vue實(shí)現(xiàn)

<template>
  <!-- z-tree遞歸組件實(shí)現(xiàn) -->
  <div>
    <ul>
      <li v-for="item in data" :key="item.label">
        {{ item.label }}
        <i class="iconfont el-icon-arrow-right" v-if="item.list"></i>
        <z-tree v-if="item.list" :data="item.list"></z-tree>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  components: {
    ZTree: () => import("./ZTree")
  },
  props: {
    data: {
      type: Array,
      required: true
    }
  },
  data() {
    return {};
  }
};
</script>

#select渲染上萬(wàn)條數(shù)據(jù)卡頓的問(wèn)題

一般下拉選擇時(shí)會(huì)使用select組件,但如果select數(shù)據(jù)過(guò)萬(wàn),可能會(huì)產(chǎn)生卡頓,其實(shí)這種數(shù)據(jù)大的情況使用select就體驗(yàn)很差了。

可以做成彈窗選擇或搜索框,那怎么保持select有數(shù)萬(wàn)條數(shù)據(jù)而不卡呢?我的理解是,使用觸底刷新,滾動(dòng)到底部時(shí)加載后面的內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容