大概有以下样式
1.基础斑马纹
2.固定状态栏 传参为(type: 'recoverState')
3.进度条 传参为
field-infos="[
{ key: 'name', label: 'XXXXX' },
{ key: 'voltage_level', label: 'XXXXX' },
{ key: 'active_power', label: 'XXXXX',tableUnit: '单位1' },
{ key: 'rated_power', label: 'XXXXX' ,tableUnit: '单位2'},
{ key: 'load_rate', label: 'XXXXX', type: 'progress', unit: '%', color: COLOR_DARK_RED },
]"
全部代码如下
import { defineComponent } from 'vue'
const mockData = [
{
name: '北京',
ratio: +2.3,
value: 178,
},
{
name: '天津',
ratio: -0.2,
value: 134,
},
{
name: '武汉',
ratio: -1.53,
value: 111,
},
{
name: '合肥',
ratio: 0.23,
value: 80,
},
{
name: '南京',
ratio: 1.7,
value: 500,
},
{
name: '武汉',
ratio: -1.53,
value: 111,
},
{
name: '合肥',
ratio: 0.23,
value: 80,
},
{
name: '南京',
ratio: 1.7,
value: 500,
},
{
name: '北京',
ratio: +2.3,
value: 178,
},
{
name: '天津',
ratio: -0.2,
value: 134,
},
{
name: '武汉',
ratio: -1.53,
value: 111,
},
{
name: '合肥',
ratio: 0.23,
value: 80,
},
{
name: '南京',
ratio: 1.7,
value: 500,
},
{
name: '武汉',
ratio: -1.53,
value: 111,
},
{
name: '合肥',
ratio: 0.23,
value: 80,
},
{
name: '南京',
ratio: 1.7,
value: 500,
},
]
const fieldInfos = [
{
key: 'name',
label: '城市',
type: '',
unit: '',
},
{
key: 'ratio',
label: '同比',
type: '',
unit: '',
color: '#02e3f4',
},
{
key: 'value',
label: '金额',
type: '',
unit: '',
color: '#02e3f4',
},
]
const TABS = ['斑马纹', '底部边框']
const props = {
// 数据
data: {
type: Array,
required: true,
default: () => mockData,
},
// 字段信息
fieldInfos: {
type: Array,
default: () => fieldInfos,
},
tableStyle: {
type: String,
default: TABS[0],
},
rowHeight: {
type: Number,
default: 112,
},
TableHeaderHeight: {
type: Number,
default: 112,
},
// 显示表头
showTableHeader: {
type: Boolean,
default: true,
},
autoScroll: {
type: Boolean,
default: true,
},
scrollSize: {
type: Number,
default: 1,
},
showTableBorder: {
type: Boolean,
default: false,
},
lazy: {
type: Boolean,
default: false,
},
currentIndex: {
type: Number,
default: -1,
},
}
export default defineComponent({
props: props,
data() {
return {
// currentIndex: -1,
items: this.data,
isScrolling: false,
scrollInterval: null,
animationFrameId: null,
}
},
computed: {
totalRows() {
return this.data.length
},
},
watch: {
autoScroll: {
handler(newVal) {
if (newVal) {
this.startAutoScroll()
} else {
this.stopAutoScroll()
}
},
immediate: true,
},
},
mounted() {},
beforeDestroy() {
this.clearTimer()
this.cancelAnimation()
},
methods: {
change(index) {
const sIndex = index === this.currentIndex ? -1 : index
this.$emit('change', sIndex)
},
// 向下滚动scrollSize行
scrollDown() {
if (this.isScrolling) return
this.isScrolling = true
const tbody = this.$refs.scrollRef
if (!tbody) {
this.isScrolling = false
return
}
const scrollDistance = this.rowHeight * this.scrollSize
const targetScrollTop = tbody.scrollTop + scrollDistance
const maxScrollTop = tbody.scrollHeight - tbody.clientHeight
// 如果到达底部,回到顶部
let finalTarget
if (targetScrollTop > maxScrollTop) {
finalTarget = 0
} else {
finalTarget = targetScrollTop
}
this.smoothScrollTo(finalTarget)
setTimeout(() => {
this.isScrolling = false
}, 300)
},
// 向上滚动scrollSize行
scrollUp() {
if (this.isScrolling) return
this.isScrolling = true
const tbody = this.$refs.scrollRef
if (!tbody) {
this.isScrolling = false
return
}
const scrollDistance = this.rowHeight * this.scrollSize
const targetScrollTop = tbody.scrollTop - scrollDistance
const maxScrollTop = tbody.scrollHeight - tbody.clientHeight
// 如果到达顶部,回到底部
let finalTarget
if (targetScrollTop < 0) {
finalTarget = maxScrollTop
} else {
finalTarget = targetScrollTop
}
this.smoothScrollTo(finalTarget)
setTimeout(() => {
this.isScrolling = false
}, 300)
},
// 平滑滚动到指定位置
smoothScrollTo(targetScrollTop) {
const tbody = this.$refs.scrollRef
if (!tbody) return
this.cancelAnimation()
const startScrollTop = tbody.scrollTop
const distance = targetScrollTop - startScrollTop
const duration = 300
let startTime = null
const animateScroll = (timestamp) => {
if (!startTime) startTime = timestamp
const elapsed = timestamp - startTime
const progress = Math.min(elapsed / duration, 1)
// 使用缓动函数
const easeProgress = 1 - Math.pow(1 - progress, 3)
tbody.scrollTop = startScrollTop + distance * easeProgress
if (progress < 1) {
this.animationFrameId = requestAnimationFrame(animateScroll)
} else {
this.animationFrameId = null
}
}
this.animationFrameId = requestAnimationFrame(animateScroll)
},
// 处理鼠标滚轮
handleWheel(event) {
event.preventDefault()
const tbody = this.$refs.scrollRef
if (!tbody) return
// 使用原生滚动,不调用自动滚动的逻辑
tbody.scrollTop += event.deltaY
},
// 开始自动滚动
startAutoScroll() {
this.clearTimer()
this.scrollInterval = setInterval(() => {
if (this.autoScroll) {
this.scrollDown()
}
}, 2000) // 每2秒滚动一次
},
// 停止自动滚动
stopAutoScroll() {
this.clearTimer()
},
handleMouseover() {
this.stopAutoScroll()
},
handleMouseout() {
if (this.autoScroll) {
this.startAutoScroll()
}
},
clearTimer() {
if (this.scrollInterval) {
clearInterval(this.scrollInterval)
this.scrollInterval = null
}
},
cancelAnimation() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId)
this.animationFrameId = null
}
},
getGridColumns(type) {
const widthList = this.fieldInfos.map((item) => {
return item?.width || '1fr'
})
const gridColumns = widthList.join(' ')
return `grid-template-columns:${gridColumns};height:${type ? this.TableHeaderHeight : this.rowHeight}px`
},
getColorByAlpha(color, alpha) {
return echarts.color.modifyAlpha(color, alpha)
},
getColorByText(fieldItem, value) {
if (fieldItem.customColor) {
return fieldItem.customColor[value] || '#fff'
}
if (fieldItem.color) {
return fieldItem.color
}
return '#fff'
},
},
})
<div
class="commont-list"
:class="{
'commont-list__border': showTableBorder,
}"
>
<div v-show="showTableHeader" class="n-list__row n-list__thead" :style="getGridColumns('header')">
<div v-for="(fieldItem, idx) in fieldInfos" :key="idx" class="n-list__item-container">
{{ fieldItem.label }}
<span v-if="fieldItem.tableUnit" style="font-size: 30px;margin-left: 10px;">({{ fieldItem.tableUnit }})</span>
</div>
</div>
<div
ref="scrollRef"
:class="{ 'no-header': !showTableHeader }"
class="n-list__tbody"
:style="`height: calc(100% - ${TableHeaderHeight}px)`"
@mouseover="handleMouseover"
@mouseout="handleMouseout"
@wheel.prevent="handleWheel"
>
<div
v-for="(item, index) in data"
:key="index"
class="n-list__row"
:class="{
selected: index === currentIndex,
'n-list__row__border': tableStyle === '底部边框',
'n-list__row__zebra': tableStyle === '斑马纹',
}"
:style="getGridColumns()"
@click="change(index)"
>
<div v-for="(fieldItem, idx) in fieldInfos" :key="idx" class="n-list__item-container">
<template v-if="fieldItem.type === 'order'">
<!-- 序号 -->
<div
class="n-list__item n-list__item-rank"
:style="{ color: fieldItem?.color || '#fff', Height: rowHeight + 'px' }"
>
{{ index + 1 }}
</div>
</template>
<template v-else-if="fieldItem.type === 'progress'">
<!-- 进度条 -->
<div
class="n-list__item n-list__item-progress"
:style="{ color: fieldItem?.color || '#fff', Height: rowHeight + 'px' }"
>
<el-progress
:stroke-width="50"
:percentage="+item[fieldItem.key]"
:style="{ color: fieldItem?.color || '#fff' }"
:color="getColorByAlpha(fieldItem.color, 0.4)"
></el-progress>
</div>
</template>
<template v-else-if="fieldItem.type === 'recoverState'">
<!-- 恢复状态 -->
<div class="n-list__item n-list__item-recover-state">
<general-indicator-card3
aria-label="指标卡片3"
:recover-state="item[fieldItem.key]"
class="boxs"
></general-indicator-card3>
</div>
</template>
<template v-else>
<!-- 普通文本 -->
<div class="n-list__item" :style="{ color: getColorByText(fieldItem, item[fieldItem.key]) }">
<!-- 文本 -->
<span class="n-list__item-text" :title="item[fieldItem.key]">
{{ item[fieldItem.key] }}{{ fieldItem.unit }}
</span>
</div></template
>
</div>
</div>
</div>
</div>
.commont-list {
width: 100%;
height: 100%;
letter-spacing: 0;
--rank-background-image: none;
--rank-background-size: 100% 100%;
--thead-color: var(--g-text-color);
--thead-bgcolor: rgba(255, 255, 255, 0.08);
--thead-fontsize: var(--g-text-fontsize);
--thead-fontweight: bold;
--margin-bottom: 0em;
--text-color: var(--g-text-color);
--text-fontsize: var(--g-text-fontsize);
--text-fontweight: normal;
--border-width: 0em 0em 0.0625em;
--border-style: solid;
--border-color: rgba(255, 255, 255, 0.1);
--line-bgcolor: rgba(137, 194, 251, 0.15);
--selected-bgcolor: rgba(0, 0, 0, 0);
box-sizing: content-box;
.text-hidden {
overflow: hidden;
white-space: nowrap;
}
.text-wrap {
display: flex;
align-items: center;
justify-content: center;
}
.text-ellipsis {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.n-list__row {
width: 100%;
display: grid;
line-height: 100%;
grid-template-columns: repeat(4, 1fr);
.n-list__item-container {
text-align: center;
.n-list__item {
width: 100%;
height: 100%;
padding: 0 0.25em;
display: flex;
align-items: center;
justify-content: center;
}
.n-list__item-rank {
.text-hidden();
.text-wrap();
background-repeat: no-repeat;
background-position: center;
}
.n-list__item-progress {
.el-progress {
// height: 60px;
width: 85%;
position: relative;
.el-progress-bar__outer {
border-radius: 0;
background-color: rgba(255, 255, 255, 0.13) !important;
}
.el-progress-bar__inner {
border-radius: 0;
.el-progress-bar__innerText {
font-size: 40px !important;
font-family: 'TRENDS';
color: inherit !important;
}
}
}
.el-progress__text {
position: absolute;
font-size: 40px !important;
font-family: 'TRENDS';
color: inherit !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.n-list__item-recover-state {
.boxs {
width: 322px;
min-width: 222px;
height: 70%;
min-height: 60px;
}
}
.n-icon {
width: 100%;
height: calc(100% - 0.375em);
margin-top: 0.1875em;
display: inline-block;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
}
}
.n-list__thead {
color: var(--thead-color);
font-size: 42px;
background-color: var(--thead-bgcolor);
font-weight: var(--thead-fontweight);
position: relative;
border-bottom: 1px solid rgba(134, 191, 250, 0.3);
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 40%;
/* 高度的30% */
background-color: white;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
.n-list__item-container {
display: flex;
align-items: center;
justify-content: center;
}
}
.n-list__tbody {
color: var(--text-color);
font-size: var(--text-fontsize);
height: calc(100% - 112px);
overflow-y: auto;
overflow-x: hidden;
scroll-behavior: smooth;
font-weight: var(--text-fontweight);
&::-webkit-scrollbar {
width: 8px; // 滚动条宽度
}
&::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.10); // 滚动条轨道背景
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: rgba(2, 227, 244, 0.5); // 蓝色滚动条滑块
border-radius: 4px;
&:hover {
background: rgba(255, 255, 255, 0.10); // 鼠标悬停时更深一点的蓝色
}
}
&::-webkit-scrollbar-corner {
background: rgba(255, 255, 255, 0.10); // 滚动条角落背景
}
&.no-header {
height: 100%;
}
.touch-bottom {
color: rgba(85, 85, 85, 0.5);
text-align: center;
font-size: 0.5em;
}
.n-list__row {
border-style: var(--border-style);
border-width: var(--border-width);
border-color: var(--border-color);
margin-bottom: var(--margin-bottom);
cursor: pointer;
&:last-child {
margin-bottom: 0;
}
&.clickable {
cursor: pointer;
}
&.selected {
background-color: var(--selected-bgcolor);
}
.n-list__item-text {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
max-width: 900px;
}
}
.n-list__row__zebra {
// 奇数行透明,偶数行保持原有背景
&:nth-child(odd) {
background-color: transparent;
}
&:nth-child(even) {
background-color: var(--line-bgcolor);
}
}
.n-list__row__border {
background-color: transparent;
border-bottom: 1px solid rgba(134, 191, 250, 0.3);
}
.n-list__row.selected {
background-color: rgba(82, 230, 255, 0.31);
border: 1px solid #00FF7D;
}
}
}
.commont-list__border {
border: 1px solid rgba(134, 191, 250, 0.3);
}