Case汇总(自用)

191 阅读5分钟

开发效率

强制commit

在git的预提交钩子中有一些代码质量检查,开发人员可以使用git commit -m "My commit message" --no-verify轻松绕过它。

页面布局

css-最后一个元素不加边框

 .data-item {
      margin-right: 20px;
      width: calc(25% - 20px);
      display: flex;
      align-items: center;
      font-size: 12px;

      &:not(:last-child) {
          border-right: rgba(97, 100, 97, 0.5) 1px solid;
      }
  }

css-一些常用自适应属性

line-height: 1.5;
margin-bottom: .25em;
max-width: calc(100% - 20px);

css--高级media

// 当页面宽度大于1920px, 安排5个视频卡
@media (min-width: 1920px) {
  .item-list .video-card.small {
    width: calc(16.7% - 25px);
  }
}

Tab吸顶布局(react)

  1. 直接给Tab加css属性sticky,兼容性不好
  2. 手动切换position属性为relative或者fixed
<div>头部</div>
<Tabs
  defaultActiveKey="1"
  className="video-detail-tabs"
  type="card"
  tabList={TABLIST}
  onChange={key => this.onTabClick(key)}
  renderTabBar={(tabBarProps, DefaultTabBar) => {
    return (
      <div className={this.state.tabClass}>
        <DefaultTabBar {...tabBarProps} />
      </div>
    );
  }}
>
  {contentList[this.activeTab - 1]}
</Tabs>
<div>内容</div>

关键在于根据滚动高度控制类名tabClass

constructor(props) {
  super(props);
  this.state = { // 1. 初始化tabClass
    tabClass:"tabArea",
  };
  this.windowOnScroll(); // 2. 绑定scroll监听
}
    

windowOnScroll = () => {
  let _this = this;
  window.onscroll = _this.throttle(_this.handleScroll, 100);
};

handleScroll = () => {
  //3. 获取滚动条滚动的距离
  let h = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
  if(h > 440){ // 这里写死了?
      this.setState({
          tabClass: "tabArea tabArea_fixed",
      });
  }else{
      this.setState({
          tabClass: "tabArea",
      });
  }
}
// 节流
throttle = (fn, delay) => {
  let lastTime = 0;
  return () => {
    let newTime = Date.now();
    if (newTime - lastTime > delay){
      fn();
      lastTime = newTime;
    }
  }
}
.tabArea{
    width: 100%;
    background-color: var(--color-bg-0);
    position: relative; 
}
.tabArea_fixed{
    z-index: 50;
    position: fixed;
    top: 50px;
}

css--解决数字和字母不换行

style="word-break:break-all;"

css——文本强制两行超出就显示省略号

  1. 强制一行的情况很简单
overflow:hidden;//超出的隐藏
text-overflow:ellipsis;//省略号
white-space:nowrap;//强制一行显示
  1. 如果要强制两行的话,得用到css3的知识
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;

在实际项目中,我们会发现-webkit-box-orient没有生效,需要加入如下注释

/*! autoprefixer: off */

  1. 最终代码:
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
overflow:hidden;
/*! autoprefixer: off */
-webkit-box-orient: vertical;

转载原文链接:blog.csdn.net/Tracy_frog/…

滚动条布局

<div className="progress">
  <div className="progress-bar-bg" id="progress-bar-bg">
      <span id="progress-dot"></span>
      <div className="progress-bar" id="progress-bar"></div>
  </div>
</div>
.progress-bar-bg {
   width: 100%;
   background-color: #C6CACD;
   position: relative;
   height: 4px;
   cursor: pointer;
 }

 .progress-bar {
   background-color: #0077FA;
   width: 20px;
   height: 4px;
 }

 #progress-dot {
   background: #FFFFFF;
   box-sizing: border-box;
   border: 1px solid rgba(0, 0, 0, 0.1);
   box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
   border-radius: 100px;
   width: 16px;
   height: 16px;
   position: absolute;
   left: 0px;
   top: -6px;
   cursor: pointer;
 }

js

跳转链接方式

  • 跳转链接 在当前窗口打开
window.location.href="http://www.baidu.com"   等价于    <a href="baidu.com" target="_self">go baidu</a> 
  • 跳转链接 在新窗口打开
window.open("http://www.baidu.com")  等价于 <a href="baidu.com" target="_blank">go baidu</a>
  • 跳转链接 上一页
window.history.back(-1);
  • 跳转链接
self.location.href="baidu.com" // self 指代当前窗口对象,属于window 最上层的对象。

sec => 00:00

/**
* 将秒转换为00:00格式
*/
secToTime = (sec = 0) => {
return ((Math.floor(sec/60))/100).toFixed(2).slice(-2)+":"+(sec % 60/100).toFixed(2).slice(-2);
}

驼峰属性和_属性名转换

recursiveTransform = (obj) => {
    let res;
    if( _.isArray(obj) && _.isObject(obj[0])) {
      res = [];
      obj.forEach(i => {
        res.push(this.recursiveTransform(i))
      })
    } else if( _.isObject(obj) ){
      res = {}
      for( let key in obj ) {
        res[key] = this.recursiveTransform(obj[key])
      }
    } else {
      res = obj;
    }
    return res;
}

camelTo_ = (name) => {
  const start = name.search(/[A-Z]/);
  if(start === -1) return name;

  const words = [name.slice(0, start)];
  name = name.slice(start);

  const regex = /([A-Z])([a-z]*)/g;
  let match = regex.exec(name);
  for (; match; match = regex.exec(name)) {
      words.push(match[1].toLowerCase() + match[2]);
  }
  return words.join('_');
}

点击下载文件

生成<a>标签,通过downloadhref属性指定下载名字和下载路径

  • So 跨域的不会直接下载
<button onClick={() => {this.download(songUrl_fix, songId)}}></button>
// 方法1: 下载文件.mp3文件会打开文件,并不是想要的浏览器直接下载的方式
download = (url, name) => {
    const a = document.createElement('a');
    a.download = `${name}.mp3`;
    a.href = url;
    document.body.appendChild(a);
    a.click();
}

// 方法2: 可以实现浏览器直接下载
download = (url, name) => {
    window.fetch(url)
      .then(response => response.blob())
      .then(blob => {
        const blobURL = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = blobURL;
        a.style = "display: none";
        a.download = `${name}.mp3`;
        document.body.appendChild(a);
        a.click();
      }).catch(err => {
        Toast.error('下载失败');
      })
};

参考源代码网址:medium.com/charisol-co…

/**
 * Modern browsers can download files that aren't from same origin this is a workaround to download a remote file
 * @param `url` Remote URL for the file to be downloaded
 */
function Download({ url, filename }) {
   const [fetching, setFetching] = useState(false);
  const [error, setError] = useState(false);

  const download = (url, name) => {
    if (!url) {
      throw new Error("Resource URL not provided! You need to provide one");
    }
    setFetching(true);
    fetch(url)
      .then(response => response.blob())
      .then(blob => {
        setFetching(false);
        const blobURL = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = blobURL;
        a.style = "display: none";

        if (name && name.length) a.download = name;
        document.body.appendChild(a);
        a.click();
      })
      .catch(() => setError(true));
  };

  return (
    <button
      disabled={fetching}
      onClick={()=> download(url, filename)}
      aria-label="download gif"
    >
      DOWNLOAD
    </button>
  );
}

React实现自定义<Audio>组件

  • 播放暂停
  • 进度条拖拽控制
  • 当前时间/总时间展示
  • 重新播放
  • 下载
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Slider, Toast } from '@ies/semi-ui-react'
import { secToTime } from '../../common/utils';

import { Icon } from '@ies/semi-ui-react';

import './index.scss';

@observer
export default class MusicCard extends Component {

  @observable value = 0;
  @observable isPlay = false;
  @observable audio = {};
  @observable current = '00:00';

  static propTypes = {
    data: PropTypes.object.isRequired,
  }

  componentDidMount() {
    this.audio = this.refs.audio;
    document.addEventListener('keydown', this.keyControl);
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.keyControl);
  }

  @computed get duration() {
    const duration = this.props.data.duration || parseInt(this.audio.duration);
    return secToTime(duration);
  }

  // 键盘监听 
  keyControl = evt => {
    if (evt.keyCode === 13) { // enter
      this.handlePlay();
    } else if (evt.keyCode === 39) { // ->
      this.quick(3);
    } else if (evt.keyCode === 37) { // <-
      this.quick(-3);
    }
  }

  // 快进、快退
  quick = (num) => {
    this.audio.currentTime +=num;
    this.timeToValue();
  }

  // 进度条控制音乐播放
  getSliderValue = (value) => {
    this.valueToTime(value);
    this.value = value;
  }

  // 进度条value 转换成 音乐播放时间
  valueToTime = (value) => {
    const current  = parseInt(value / 100 * this.audio.duration); // 计算currentTime
    this.audio.currentTime = current; // 设置currentTime
    this.current = secToTime(current); // 更新当前时间显示
  }

  // 音乐播放时间 转换为 进度条value
  timeToValue = () => {
    this.current = secToTime(this.audio.currentTime);
    this.value = parseInt(this.audio.currentTime/this.audio.duration * 100);
  }

  // 暂停、播放控制
  handlePlay = () => {
    if(this.value == 100){ // 进度条满后,点击按钮,重新播放音乐
      this.initPlay();
      this.play();
      return;
    }
    if(this.isPlay){
      this.pause();
    } else {
      this.play();
    }
  }

  onPlay = () => {
    this.isPlay = true;
  }

  onPause = () => {
    this.isPlay = false;
  }

  play = () => {
    this.audio.play();
  }

  pause = () => {
    this.audio.pause();
  }

  onTimeUpdate = (e) => {
    this.timeToValue();
  }

  onEnded = () => {
    this.onPause();
  }

  initPlay = () => {
    this.audio.currentTime = 0;
    this.value = 0;
  }

  // 重新播放
  rePlay = () => {
    this.initPlay();
    this.onPlay();
    this.play();
  }

  // 下载文件
  download = (url, name) => {
    window.fetch(url)
      .then(response => response.blob())
      .then(blob => {
        const blobURL = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = blobURL;
        a.style = "display: none";
        a.download = `${name}.mp3`;
        document.body.appendChild(a);
        a.click();
      }).catch(err => {
        Toast.error('下载失败');
      })
  };

  render() {
    const { songUrl, songId } = this.props.data;
    const songUrl_fix = songUrl.replace('http:','https:');
    return(
      <div className="audio-wrap"> 
        <audio 
          ref="audio" 
          src={songUrl} 
          type="audio/mp3" 
          onPlay={this.onPlay} 
          onPause={this.onPause} 
          onTimeUpdate={this.onTimeUpdate} 
          onEnded={this.onEnded}>
        </audio>
        <Slider step={1} max={100} value={this.value} tipFormatter={null} onChange={(value) => (this.getSliderValue(value))} ></Slider>
        <div className="controls">
          <div className="controls-left">
            <div className="icon-wrap" onClick={this.handlePlay}>
              {
                this.isPlay ? <Icon type="pause" size="small"/> : <Icon type="play" size="small"/>
              }
            </div>
            <div className="icon-wrap" onClick={this.rePlay}><Icon type="refresh" size="small" /></div>
            <div className="time-wrap"><span>{ this.current }</span>/<span>{ this.duration }</span></div>
          </div>
          <div className="controls-right">
            <div className="icon-wrap"><Icon type="volume_2" size="small"/></div>
            <div className="icon-wrap" onClick={() => {this.download(songUrl_fix, songId)}}><Icon type="download" size="small"/></div>
          </div>
        </div>
      </div>
    )
  }
}

segmentfault.com/a/119000001…

技术方案

全局loading控制初始组件渲染

场景:初始路由组件渲染的数据是异步请求得到的,为了解决在每个路由组件初始渲染时拿不到数据的问题,引入全局loading

  • 全局loading只控制全局数据加载,在全局数据获取到后渲染子组件
  • 每个子组件内部可根据情况维护自身的loading
  • 初始全局loading最好设置为true,否则可能会出现子组件请求两次的情况(若初始化为false,则初始化和全局数据异步取得后都进行了子组件渲染)