前端项目经验总结
UI组件
v-infinite-scroll 无限滚动
需求:需要设置一个列表,滚动到最底部请求额外数据
<ul
class="scroll"
v-infinite-scroll="loadMoreData"
infinite-scroll-immediate="false"
infinite-scroll-distance="1"
infinite-scroll-delay="300"
:infinite-scroll-disabled="busy"
>
通过v-infinite-scroll 每次滚动到最底部的时候触发对应的laodMoreData方法,
同时传入新的page,获取另外50条数据,再通过[...oldValue,...newValue]进行重赋值
Bug:
①如果不设置infinite-scroll-distance="1" 那么滚动到最底部的时候有且仅触发1次,或两次,之后滚动不会触发。
②如果不设置immeidate:false,那么会立刻触发一次
③需要进行一个节流,因为你设置distance:1后,滚动一次会自动触发两次,你要求300ms内有且仅能触发一次。注意!必须给滚动容器添加v-infinite-scroll不然会有bug
api
1、v-infinite-scroll="loadMore":表示回调函数是loadMore
2、infinite-scroll-disabled="busy":表示由变量busy决定是否执行loadMore,false则执行loadMore,true则不执行,注意,busy表示繁忙,繁忙的时候是不执行的。
2、infinite-scroll-distance="10":这里10决定了页面滚动到离页尾多少像素的时候触发回调函数,10是像素值。一般情况下会在页尾做一个几十像素高的“正在加载中...”,这样的话,可以把这个div的高度设为infinite-scroll-distance的值即可。
3、infinite-scroll-immediate:false:默认值为true,该指令表示,应该在绑定后立即检查busy的值和是否滚动到底。假如你的初始内容高度不够,不足以填满可滚动的容器的话,你应设为true,这样会立即执行一次loadMore,会帮你填充一些初始内容。
4、infinite-scroll-listen-for-event:当事件在Vue实例中发出时,无限滚动将再次检查。
5、infinite-scroll-delay:即节流
自封装虚拟列表
1、计算出容器的容积,通过ref获取到容器,从而获取到高度 refs.xx.offsetHeight
2、计算出容器最大存放几条列表数据(前提是这个列表数据每条都必须相同,记录单条列表数据contentHeight+padding+border+margin),
3、
项目所遇
checkbox 复选与全选
有一个很重要的知识点:原生的checkbox是没法直接修改样式的,一般是模拟checkbox,或者直接使用UI组件库的
可以通过ant-desgin里的a-checkbox ,实现类似于checkbox的效果
实现全选
//传入的值
preference: {
iciCode: {
name: "iciCode",
activeIndex: -1,
indeterminate: true,
list: [
{ label: "人工智能", value: "AI" },
{ label: "生物医药", value: "MAH" },
{ label: "虚拟现实", value: "MVS" },
{ label: "现代金融", value: "MF" },
{ label: "软件信息服务", value: "NGIT" },
{ label: "文化旅游", value: "CULT" },
{ label: "工业互联网及智能制造", value: "gyhlw" },
],
},
//template 模板
<a-checkbox
:checked="allIsChecked(preference.iciCode.name)"
@change="allNodeChange($event, preference.iciCode.name)"
:indeterminate="preference.iciCode.indeterminate"
>
全部
</a-checkbox>
<a-checkbox
v-for="(item, index) in preference.iciCode.list"
:key="index"
:checked="query.iciCode.includes(item.value)"
@change="smallNodeChange($event, item.value, preference.iciCode.name)"
>
{{ item.label }}
</a-checkbox>
methods:{
//小节点
smallNodeChange(e, value, key) {
if (e.target.checked) {
this.query[key].push(value);
} else {
//过滤掉未选中项
this.query[key] = this.query[key].filter((item) => item != value);
}
this.preference[key].indeterminate =
!!this.query[key].length &&
this.query[key].length < this.preference[key].list.length;
console.log(this.query[key]);
},
//all
allNodeChange(e, key) {
this.preference[key].indeterminate = false;
if (e.target.checked) {
this.query[key] = this.preference[key].list.map((item) => item.value);
} else {
this.query[key] = [];
}
console.log(this.query[key]);
},
//全选样式
allIsChecked(key) {
return this.query[key].length == this.preference[key].list.length;
},
}
!!因为是多选,所以一般会用[]进行存储相对的value,这样可以通过includes判断某个复选框是否checked
//通过v-for来遍历list,获取到每一个对应的label和value,通过受控的选中数组.includes(value)来判断当前
项是否被选中
<a-checkbox v-for="(item, index) in preference.iciCode.list"
:key="index"
:checked="query.iciCode.includes(item.value)"
@change="smallNodeChange($event, item.value, preference.iciCode.name)" >
通过change方法,进行修改。首先通过$event.target.checked,判断点击时是何状态,如果是选中, 那么直接往受控的选中数组里添加(includes就可以使得:checked=true,实现选中样式), 如果是非选中,那么过滤受控的选中数组即可~
v-for循环赋值不同的背景图
<ul>
<li
v-for="(item, index) in InfoList"
:key="item.id"
class="listItem"
:class="{
[`bg${item.id}`]: true,
[`activeBg${item.id}`]: index == activeIndex,
}"
@click="changeActive(index, item.name)"
>
<span>{{ item.text }}</span>
</li>
</ul>
传入的时候需要将图片id与bg${id}对应
.bg26 {
background-image: url("../assets/images/yxgj.png");
&:hover {
background-image: url("../assets/images/active_yxgj.png");
}
}
.activeBg1 {
background-image: url("../assets/images/active_rgzn.png");
}
ecahrts图表
自定义数据项
场景:例如雷达图,我们自定义tooltip的时候,需要获取到axisName等属性,但是雷达图的formatter方法params参数没有对应的值,这时候我们可以根据series.data里的顺序额外定义参数,然后通过watch监听options配置项,传入到defaultOptions中,这样params.data中就可以获取到自定义属性
<template>
<div
:class="id == 'evalute-radar' ? 'radar-base' : 'radar-portrait'"
:id="id"
:style="{ height: height, width: width }"
></div>
</template>
<script>
import * as echarts from "echarts";
export default {
props: {
id: {
type: String,
default: "radar",
},
width: {
type: String,
default: "100%",
},
height: {
type: String,
default: "100%",
},
options: {
type: Object,
default: () => {
return {
indicator: [],
seriesData: [],
};
},
},
},
data() {
return {
styleType: 0,
lineColor: ['#5BEBFF', '#FF9C5B'],
areaColor: ['rgba(55,195,233,0.57)', 'rgba(233,104,55,0.57)'],
myChart: null,
defaultOptions: {
color: ["#67F9D8"],
tooltip: {
trigger: "item",
position: function (pos, params, dom, rect, size) {
// 鼠标在左侧时 tooltip 显示到右侧,鼠标在右侧时 tooltip 显示到左侧。
var obj = { top: 60 };
obj[["left", "right"][+(pos[0] < size.viewSize[0] / 2)]] = 5;
return obj;
},
backgroundColor: "#143d7b",
borderColor: "#2d91c5",
textStyle: {
color: "#fff",
},
formatter: (params) => {
let result = "<ul class='line-tool-tip'>";
const { data, value } = params;
const { text, max } = data;
for (let i = 0; i < value.length; i++) {
let node = `<div class='radar-item'>
<span class="radar-tag" style="background-color: ${this.styleType == 1 ? this.lineColor[1] : this.lineColor[0]}"></span>
${text[i]}:${this.formatterNum(value[i], max[i])}分
</div>`;
result += node;
}
result += "</ul>";
return result;
},
},
// legend: {},
radar: [
{
indicator: [],
center: ["50%", "50%"],
radius: 70, //维度各个点距离中心点的距离
nameGap: 20, //维度名称距离中心点的距离
startAngle: 90,
splitNumber: 3,
shape: "circle",
axisName: {
color: "#fff",
fontsize: 16,
},
splitArea: {
show: false,
areaStyle: {
color: ["#1b4f96", "#123c7e", "#1c4d9e"],
shadowColor: "rgba(132, 188, 241, 1)",
shadowBlur: 20,
},
},
axisLine: {
show: false,
lineStyle: {
color: "#ff0000",
type: "dashed",
width: 1.5,
},
symbol: ["none", "arrow"],
},
splitLine: {
show: false,
lineStyle: {
color: ["#2160b5", "#2654ba", "#3d6eda"],
shadowColor: "#1f5ba4",
shadowBlur: 10,
shadowOffsetX: -20,
opacity: 0.6,
},
},
},
],
series: [
{
type: "radar",
emphasis: {
lineStyle: {
width: 4,
},
},
data: [
{
value: [],
name: "",
// lineStyle: {
// color: '#FF9C5B',
// width: 4
// },
itemStyle: {
color: '#FFFFFF'
},
// areaStyle: {
// color: "rgba(233,104,55,0.57)",
// },
text: [],
max: [],
},
],
},
],
},
};
},
watch: {
options: {
immediate: true,
handler: function (newValue) {
if (newValue) {
if (this.myChart) {
this.myChart.dispose();
}
this.styleType = newValue.styleType;
const max = [];
const text = [];
newValue.indicator.forEach((item) => {
max.push(item.max);
text.push(item.text);
});
this.defaultOptions.radar[0].indicator = newValue.indicator;
this.defaultOptions.series[0].data[0].value = newValue.seriesData;
this.defaultOptions.series[0].data[0].text = text;
this.defaultOptions.series[0].data[0].max = max;
this.defaultOptions.series[0].data[0].lineStyle = {
width: 4,
color:
newValue.styleType == 1 ? this.lineColor[1] : this.lineColor[0],
};
this.defaultOptions.series[0].data[0].areaStyle = {
color:
newValue.styleType == 1 ? this.areaColor[1] : this.areaColor[0],
};
this.defaultOptions.tooltip.borderColor = newValue.styleType == 1 ? this.lineColor[1] : this.lineColor[0]
this.$nextTick(() => {
this.myChart = echarts.init(document.getElementById(this.id));
this.myChart.setOption(this.defaultOptions);
});
}
},
deep: true,
},
},
mounted() {
window.addEventListener("resize", () => this.myChart.resize());
},
methods: {
formatterNum(num, max) {
return Math.round((num / max) * 100);
},
},
};
</script>
正常情况下,series的data中是没有text,max等属性的,可以自定义。再在watch中监听传入的值。
有个细节是newValue.xxx.forEach。这是因为如果有多条数据,是以数组形式进行传递的,可以给defaultOptions中配置多条数据,实现多个图表