目录
[debounce 函数](#debounce 函数)
[【扩展】vue 函数式组件](#【扩展】vue 函数式组件)
组件封装
这段代码是一个封装的分页表格组件。它使用了Vue.js框架,并结合了Element UI中的el-table和el-pagination组件来实现。该组件接受一系列属性作为数据源,包括列表数据(list)、表格列(columns)、操作按钮列(operates)、总数(total)、分页参数(pagination)等。
在模板中,使用el-table组件来展示表格数据,使用el-pagination组件来实现分页功能。el-table-column用来定义每列的样式和展示内容。组件中还包括其他的一些方法和事件处理函数,如handleSizeChange用于切换每页显示的数量,handleIndexChange用于切换页码,handleSelectionChange处理多行选中等。
此外,还定义了一些样式规则来美化表格的显示效果,如设置表头颜色、调整列的样式、设置操作按钮组的布局、设置筛选弹窗和表格操作弹窗的样式等。
最后,使用了scss来编写样式表,通过设置类名来控制样式的展示。
-
在
<template>
标签中,定义了一个表格组件,包括表格本身,列定义,按钮操作组,和分页控件。 -
在
<script>
标签中,导入了必要的依赖,并定义了组件的属性(props)、数据(data)、生命周期钩子(activated、beforeDestroy、deactivated、created、mounted)和方法(methods)。 -
props
属性用于接受从父组件传递进来的数据,如数据列表list
、列定义columns
、按钮操作operates
、总数total
、分页参数pagination
等。 -
data
数据属性包括一些内部状态,如表格高度height
、监听窗口调整大小的回调函数$_resizeHandler
、当前页码pageIndex
、表格分页信息tableCurrentPagination
、多行选中数据multipleSelection
等。 -
在生命周期钩子中,组件监听窗口大小调整事件,自动调整表格高度,并在适当的生命周期中初始化和销毁事件监听器。
-
methods
包括一系列方法,如计算表头宽度的方法headSpanFit
、初始化事件监听器的方法initListener
、销毁事件监听器的方法destroyListener
、调整表格高度的方法resize
、处理每页显示数量变化的方法handleSizeChange
、处理页码变化的方法handleIndexChange
、处理多行选中的方法handleSelectionChange
等。 -
最后,在
<template><style>
标签中定义了一些样式,包括表格样式、分页样式、按钮样式、筛选和操作弹窗样式等。<el-table id="iTable" v-loading.iTable="options.loading" :data="list" :stripe="options.stripe" ref="mutipleTable" @selection-change="handleSelectionChange" :height="noStatic ? customHeight : height" :max-height="noStatic ? customHeight : height"> <el-table-column v-if="options.mutiSelect" type="selection" style="width: 55px"> </el-table-column> <template v-for="(column, index) in columns"> <el-table-column :min-width="headSpanFit(column)" :prop="column.prop" :label="column.label" :align="column.align" :width="column.width"> <template slot-scope="scope"> <template v-if="!column.render"> <template v-if="column.formatter"> </template> <template v-else> {{ scope.row[column.prop] === 0 ? 0 : scope.row[column.prop] || "--" }} </template> </template> <template v-else> <expand-dom :column="column" :row="scope.row" :render="column.render" :index="index"></expand-dom> </template> </template> </el-table-column> </template> <el-table-column ref="fixedColumn" label="操作" align="center" :width="operates.width" :fixed="operates.fixed" v-if="operates.list.length > 0"> <template slot-scope="scope"></template> <script> import { debounce } from "@/utils"; const _pageArray = [20, 50, 100]; // 每页展示条数的控制集合 export default { props: { list: { type: Array, default: [], // prop:表头绑定的地段,label:表头名称,align:每列数据展示形式(left, center, right),width:列宽 }, // 数据列表 columns: { type: Array, default: [], // 需要展示的列 === prop:列数据对应的属性,label:列名,align:对齐方式,width:列宽 }, operates: { type: Object, default: {}, // width:按钮列宽,fixed:是否固定(left,right),按钮集合 === label: 文本,type :类型(primary / success / warning / danger / info / text),show:是否显示,icon:按钮图标,plain:是否朴素按钮,disabled:是否禁用,method:回调方法 }, total: { type: Number, default: 0, }, // 总数 pagination: { type: Object, default: null, // 分页参数 === pageSize:每页展示的条数,pageIndex:当前页,pageArray: 每页展示条数的控制集合,默认 _page_array }, noStatic: false, // 是否计算表格高度 customHeight: { //与noStatic一起使用 type: Number, default: 320, }, otherHeight: { type: Number, default: 180, }, // 计算表格的高度 options: { type: Object, default: { stripe: false, // 是否为斑马纹 table loading: false, // 是否添加表格loading加载动画 highlightCurrentRow: false, // 是否支持当前行高亮显示 mutiSelect: false, // 是否支持列表项选中功能 }, }, // table 表格的控制参数 }, components: { expandDom: { functional: true, props: { row: Object, render: Function, index: Number, column: { type: Object, default: null, }, }, render: (h, ctx) => { const params = { row: ctx.props.row, index: ctx.props.index, }; if (ctx.props.column) params.column = ctx.props.column; return ctx.props.render(h, params); }, }, }, data() { return { height: 250, $_resizeHandler: null, pageIndex: 1, tableCurrentPagination: {}, multipleSelection: [], // 多行选中 }; },<template v-for="(btn, key) in operates.list"></template> </el-table-column> </el-table> <el-pagination v-if="pagination" @size-change="handleSizeChange" @current-change="handleIndexChange" :page-size="tableCurrentPagination.pageSize" :page-sizes="this.tableCurrentPagination.pageArray" :current-page="tableCurrentPagination.pageIndex" layout="total,sizes, prev, pager, next,jumper" :total="total"></el-pagination><el-button :type="btn.type" :key="key + 'itbtn'" :size="btn.size || 'mini'" :icon="btn.icon" :disabled="typeof btn.disabled == 'function' ? btn.disabled(scope.$index, scope.row) : btn.disabled" :plain="btn.plain" @click.native.prevent="btn.method(scope.$index, scope.row)">{{ btn.label }} </el-button></template>activated() {
// 通常是在使用 Vue.js 的 <keep-alive> 包装时,组件会被缓存并在重新激活时调用这个钩子函数。
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener();
}// when keep-alive chart activated, auto resize this.resize();
},
//当组件即将被销毁(beforeDestroy)时,会调用这个钩子函数。
beforeDestroy() {
this.destroyListener();
},
//当组件被停用(deactivated),通常也是在 <keep-alive> 包装下,组件会调用这个钩子函数。
deactivated() {
this.destroyListener();
},
created() { },
mounted() {
this.initListener();
if (this.pagination && !this.pagination.pageSizes) {
this.pagination.pageArray = _pageArray; // 每页展示条数控制
}
this.tableCurrentPagination = this.pagination || {
pageSize: this.total,
pageIndex: 1,
}; // 判断是否需要分页
},
methods: {
//计算小列宽
headSpanFit(column) {
let labelLong = column.label.length; // 表头label长度
let size = 20; // 根据需要定义标尺,直接使用字体大小确定就行,也可以根据需要定义
let minWidth = labelLong * size < 100 ? 100 : labelLong * size; // 根据label长度计算该表头最终宽度
return minWidth;
},
// 初始化监听器的方法。
initListener() {
this._resizeHandler = debounce(() => { this.resize(); }, 200); window.addEventListener("resize", this._resizeHandler);
this.nextTick(() => { this.resize(); }); }, //这是一个销毁监听器的方法。 destroyListener() { window.removeEventListener("resize", this._resizeHandler);
this.$_resizeHandler = null;
},
//计算表格的高度
resize() {
// 不需要计算
if (this.noStatic) {
return;
}const { mutipleTable } = this.$refs; // 窗口的高度减去表格的顶部偏移 let staticHeight = window.innerHeight - this.$refs.mutipleTable.$el.offsetTop - this.otherHeight; this.height = staticHeight < 250 ? 250 : staticHeight; console.log(this.height); //保表格的布局在高度调整后得以更新。 mutipleTable && mutipleTable.doLayout(); }, // 切换每页显示的数量 handleSizeChange(size) { if (this.pagination) { this.tableCurrentPagination = { pageIndex: 1, pageSize: size, }; this.$emit("handleSizeChange", this.tableCurrentPagination); } }, // 切换页码 handleIndexChange(currnet) { if (this.pagination) { this.tableCurrentPagination.pageIndex = currnet; this.$emit("handleIndexChange", this.tableCurrentPagination); } }, // 多行选中 handleSelectionChange(val) { this.multipleSelection = val; this.$emit("handleSelectionChange", val); }, // 显示 表格操作弹窗 showActionTableDialog() { this.$emit("handelAction"); },
},
<style lang="scss"> .table { height: 100%;
};
</script>.el-pagination {
float: right;
margin: 20px;
}.el-table__header-wrapper,
.el-table__fixed-header-wrapper {
thead {
tr {
th {
color: #333333;
}
}
}
}.el-table-column--selection .cell {
padding: 0;
text-align: center;
}.el-table__fixed-right {
bottom: 0 !important;
right: 6px !important;
z-index: 1004;
}.operate-group {
display: flex;
flex-wrap: wrap;.item { margin-top: 4px; margin-bottom: 4px; display: block; flex: 0 0 50%; }
}
.filter-data {
top: e("calc((100% - 100px) / 3)");
background-color: rgba(0, 0, 0, 0.7);
}.table-action {
top: e("calc((100% - 100px) / 2)");
background-color: rgba(0, 0, 0, 0.7);
}.fix-right {
position: absolute;
right: 0;
height: 100px;
color: #ffffff;
width: 30px;
display: block;
z-index: 1005;
writing-mode: vertical-rl;
text-align: center;
line-height: 28px;
border-bottom-left-radius: 6px;
border-top-left-radius: 6px;
cursor: pointer;
}
}
</style>
parseTime函数
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
debounce 函数
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
页面使用
<template>
<div class="table-page">
<!--region table 表格-->
<app-table :list="list" :total="total" :otherHeight="otherHeight" :options="options" :pagination="pagination"
:columns="columns" :operates="operates" @handleSizeChange="handleSizeChange" @handleIndexChange="handleIndexChange"
@handleSelectionChange="handleSelectionChange">
</app-table>
<!--endregion-->
</div>
</template>
<script>
import {
parseTime,
} from "@/utils/ruoyi";
export default {
data() {
return {
total: 0,
list: [
{
id: 1,
title: '标题',
state: 0,
author: '张三',
phone: '12346788901',
email: '1234556778@qq.com',
createDate: "2023-04-23 16:11:38"
}
],
otherHeight: 208,
columns: [
{
prop: 'id',
label: '编号',
align: 'center',
width: 60
},
{
prop: 'title',
label: '标题',
align: 'center',
width: 400,
formatter: (row, column, cellValue) => {
return `<span style="white-space: nowrap;color: dodgerblue;">${row.title}</span>`
}
},
{
prop: 'state',
label: '状态',
align: 'center',
width: '160',
render: (h, params) => {
return h('el-tag', {
props: { type: params.row.state === 0 ? 'success' : params.row.state === 1 ? 'info' : 'danger' } // 组件的props
}, params.row.state === 0 ? '上架' : params.row.state === 1 ? '下架' : '审核中')
}
},
{
prop: 'author',
label: '作者',
align: 'center',
width: 120
},
{
prop: 'phone',
label: '联系方式',
align: 'center',
width: 160
},
{
prop: 'email',
label: '邮箱',
align: 'center',
width: 240
},
{
prop: 'createDate',
label: '发布时间',
align: 'center',
width: 180,
formatter: (row, column, cellValue) => {
return parseTime(row.createDate)
}
}
], // 需要展示的列
operates: {
width: 200,
fixed: 'right',
list: [
{
label: '编辑',
type: 'warning',
show: (index, row) => {
return true
},
icon: 'el-icon-edit',
plain: true,
disabled: false,
method: (index, row) => {
this.handleEdit(index, row)
}
},
{
label: '删除',
type: 'danger',
icon: 'el-icon-delete',
show: true,
plain: false,
disabled: (index, row) => {
return false
},
method: (index, row) => {
this.handleDel(index, row)
}
}
]
}, // 操作按钮组
pagination: {
pageIndex: 1,
pageSize: 20
}, // 分页参数
options: {
stripe: true, // 是否为斑马纹 table
loading: false, // 是否添加表格loading加载动画
highlightCurrentRow: true, // 是否支持当前行高亮显示
mutiSelect: true // 是否支持列表项选中功能
} // table 的参数
}
},
components: {
expandDom: {
props: {
column: {
required: true
},
row: {
required: true
}
},
render(h) {
return h('div', {}, ([this.column.render(this.row, this.column)]))
}
}
},
mounted() {
},
methods: {
// 切换每页显示的数量
handleSizeChange(pagination) {
this.pagination = pagination
},
// 切换页码
handleIndexChange(pagination) {
this.pagination = pagination
},
// 选中行
handleSelectionChange(val) {
console.log('val:', val)
},
// 编辑
handleEdit(index, row) {
console.log(' index:', index)
console.log(' row:', row)
},
// 删除
handleDel(index, row) {
console.log(' index:', index)
console.log(' row:', row)
},
}
}
</script>
【扩展】vue 函数式组件
函数式组件特点:
- 没有管理任何状态
- 没有监听任何传递给它的状态
- 没有生命周期方法
- 它只是接收一些
prop
的函
我们将这样的组件标记为functional
:
- 无状态 == 无响应式数据
- 无实例 == 无
this
上下文
函数式组件的优点:
-
渲染开销低,因为函数式组件只是函数;
{
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
}
props
: 提供所有prop
的对象
children:VNode
子节点的数组
slots
: 一个函数,返回了包含所有插槽的对象
scoptedSlots
:(2.6.0) 一个暴露传入的作用域插槽的对象,也以函数形式暴露普通插槽
data
:传递个组件的整个数据对象,作为createElement
的第二个参数传入组件
parent
:对父组件的引用
listeners
:(2.3.0+) 一个包含了:所有父组件为当前组件祖册的事件监听器对象,是data.on
的一个别名
injections
:(2.3.0+) 如果使用了inject
选项,则改对象包含了:应当被注入的属性;
【扩展】vue中的render函数
一、初步认识render函数
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App)
})
在使用脚手架创建vue项目的过程,我们很容易看到render这个函数,相对于其他标签,我们对于render还是比较陌生的,因此写下这篇文章你我共同理解。
二、为什么使用render函数
VUE推荐在绝大多数情况下使用template来创建我们的HTML。然而在一些场景中,我们真的需要JavaScript的完全编程的能力,这就是render函数,它比template更接近编译器。(这是官方的话)
简单来说,我们为什么要使用render函数呢?? 便是因为我们最经常使用的一个引入。
import Vue from "vue";
这一个引入你看似没有任何问题,但问题恰恰就是出在这。在不同版本的vue中,有vue.js和vue.runtime.xxx.js这两种js文件。其中
(1)vue.js是完整版的vue,包含核心功能+模板解析器。
(2)vue.runtime.xxx.js是运行版vue,只包含核心功能,没有模板解析器。
VUE开发者为了让我们打包的文件能尽可能小一点,在上述引入的是运行版vue。因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,这时候就需要使用render函数去接收到的createElement函数去指定具体内容,创建html模板。
三、render函数的解析
render 函数即渲染函数,它是个函数,它的参数 createElement 也是个函数。
上边的代码中 render: h => h(App) ,这是 ES6的箭头函数的写法,可以把 h 当作 createElement 的别名。所以这段代码其实相当于
render: function (createElement) {
return createElement(App)
}
这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
createElement 函数的返回值是 VNode(即:虚拟节点)
createElement 函数的3个参数
-
一个 HTML 标签字符串,组件选项对象,或者解析上述任何一种的一个 async 异步函数。类型:String | Object | Function。必需。
-
一个包含模板相关属性的数据对象,你可以在 template 中使用这些特性。类型:Object。可选。
-
子虚拟节点 (VNodes),由 createElement() 构建而成,也可以使用字符串来生成"文本虚拟节点"。类型:String | Array。可选
new Vue({
el: '#app',
render:function (createElement) {
//1.普通用法
// createElement(标签,{属性},[内容])
return createElement("h2",{class:"box"},['hello',createElement("button",["按钮"])])
}
})
同时createElement也可以传进去一个组件,因此
render: h => h(App)
等同于
render:function (createElement) {
return createElement(App)
}