开发效率
强制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)
- 直接给Tab加css属性sticky,兼容性不好
- 手动切换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——文本强制两行超出就显示省略号
- 强制一行的情况很简单
overflow:hidden;//超出的隐藏
text-overflow:ellipsis;//省略号
white-space:nowrap;//强制一行显示
- 如果要强制两行的话,得用到css3的知识
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
在实际项目中,我们会发现-webkit-box-orient没有生效,需要加入如下注释
/*! autoprefixer: off */
- 最终代码:
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>
标签,通过download
和href
属性指定下载名字和下载路径
- 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>
)
}
}
技术方案
全局loading控制初始组件渲染
场景:初始路由组件渲染的数据是异步请求得到的,为了解决在每个路由组件初始渲染时拿不到数据的问题,引入全局loading
- 全局loading只控制全局数据加载,在全局数据获取到后渲染子组件
- 每个子组件内部可根据情况维护自身的loading
- 初始全局loading最好设置为true,否则可能会出现子组件请求两次的情况(若初始化为false,则初始化和全局数据异步取得后都进行了子组件渲染)